LP1904788 Browse paging show next/prev WIP user/berick/lp1904788-staffcat-browse-paging
authorBill Erickson <berickxx@gmail.com>
Thu, 19 Nov 2020 22:57:01 +0000 (17:57 -0500)
committerBill Erickson <berickxx@gmail.com>
Thu, 19 Nov 2020 22:57:01 +0000 (17:57 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/browse-pager.component.html
Open-ILS/src/eg2/src/app/staff/catalog/result/browse-pager.component.ts
Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts

index 52e1173..c828c16 100644 (file)
@@ -46,7 +46,7 @@ export class StaffCatalogService {
 
     // Cache of browse results so the browse pager is not forced to
     // re-run the browse search on each navigation.
-    browsePagerResults: any[];
+    browsePagerData: any[];
 
     constructor(
         private router: Router,
index 6b46379..8d592da 100644 (file)
     </div>
     <div class="col-lg-4 d-flex justify-content-center">
       <div class="pt-1">
-        Heading: <span class="font-weight-bold">
+        <ng-container [ngSwitch]="searchContext.browseSearch.fieldClass">
+          <span i18n *ngSwitchCase="'title'">Title: </span>
+          <span i18n *ngSwitchCase="'author'">Author: </span>
+          <span i18n *ngSwitchCase="'subject'">Subject: </span>
+          <span i18n *ngSwitchCase="'series'">Series: </span>
+        </ng-container>
+        <span class="font-weight-bold">
           {{searchContext.termSearch.browseEntry.value()}}
         </span>
       </div>
index 83054b6..92fb407 100644 (file)
@@ -7,6 +7,12 @@ import {StaffCatalogService} from '../catalog.service';
 import {IdlObject} from '@eg/core/idl.service';
 import {BasketService} from '@eg/share/catalog/basket.service';
 
+interface BrowsePage {
+    leftPivot: number;
+    rightPivot: number;
+    entries: any[];
+}
+
 @Component({
   selector: 'eg-catalog-browse-pager',
   templateUrl: 'browse-pager.component.html'
@@ -28,31 +34,53 @@ export class BrowsePagerComponent implements OnInit {
         this.fetchPageData().then(_ => this.setPrevNext());
     }
 
-    fetchPageData(): Promise<any> {
+    pageEntryId(): number {
+        return Number(
+            this.searchContext.termSearch.hasBrowseEntry.split(',')[0]
+        );
+    }
 
-        let found = false;
-        const mbeId = this.pageEntryId();
-        this.staffCat.browsePagerResults.forEach(result => {
-            if (result.browse_entry === mbeId) {
-                found = true;
-            }
+    getEntryPageIndex(mbeId: number): number {
+        let idx = null;
+        this.staffCat.browsePagerData.forEach((page, index) => {
+            page.entries.forEach(entry => {
+                if (entry.browse_entry === mbeId) {
+                    idx = index;
+                }
+            });
         });
+        return idx;
+    }
+
 
-        if (found) {
+    getEntryPage(mbeId: number): BrowsePage {
+        return this.staffCat.browsePagerData[this.getEntryPageIndex(mbeId)];
+    }
+
+    fetchPageData(): Promise<any> {
+
+        if (this.getEntryPage(this.pageEntryId())) {
+            // We have this page's data already
             return Promise.resolve();
-        } else {
-            return this.fetchBrowseData(null);
         }
+
+        return this.fetchBrowsePage(null);
     }
 
     // Grab a page of browse results
-    fetchBrowseData(prev: boolean): Promise<any> {
+    fetchBrowsePage(prev: boolean): Promise<any> {
         const ctx = this.searchContext.clone();
         ctx.pager.limit = this.searchContext.pager.limit;
         ctx.termSearch.hasBrowseEntry = null; // avoid term search
 
         if (prev !== null) {
-            ctx.browseSearch.pivot = this.getBoundaryPivot(prev);
+            const page = this.getEntryPage(this.pageEntryId());
+            const pivot = prev ? page.leftPivot : page.rightPivot;
+            if (pivot === null) {
+                console.debug('Browse has reached the end of the rainbow');
+                return;
+            }
+            ctx.browseSearch.pivot = pivot;
         }
 
         const results = [];
@@ -61,120 +89,119 @@ export class BrowsePagerComponent implements OnInit {
         return this.cat.browse(ctx)
         .pipe(tap(result => results.push(result)))
         .toPromise().then(_ => {
+            if (results.length === 0) { return; }
+
+            // At the end of the data set, final pivots are not present
+            let leftPivot = null;
+            let rightPivot = null;
+            if (results[0].pivot_point) {
+                leftPivot = results.shift().pivot_point;
+            }
+            if (results[results.length - 1].pivot_point) {
+               rightPivot = results.pop().pivot_point;
+            }
+
+            // We only care about entries with bib record sources
+            let keepEntries = results.filter(e => Boolean(e.sources))
+
+            if (leftPivot === null || rightPivot === null) {
+                // When you reach the edge of the data set, you can get
+                // the same browse entries from different API calls.
+                // From what I can tell, the last page will always have
+                // 5 entries, even if you've already seen some of those
+                // entries.  Trim the dupes since they affect the logic.
+                const keep = [];
+                keepEntries.forEach(e => {
+                    if (!this.getEntryPage(e.browse_entry)) {
+                        keep.push(e);
+                    }
+                });
+                keepEntries = keep;
+            }
+
+            const page: BrowsePage = {
+                leftPivot: leftPivot,
+                rightPivot: rightPivot,
+                entries: keepEntries
+            };
+
             if (prev) {
-                this.staffCat.browsePagerResults =
-                    results.concat(this.staffCat.browsePagerResults);
+                this.staffCat.browsePagerData.unshift(page);
             } else {
-                this.staffCat.browsePagerResults =
-                    this.staffCat.browsePagerResults.concat(results);
+                this.staffCat.browsePagerData.push(page);
             }
             this.browseLoading = false;
         });
     }
 
-    pageEntryId(): number {
-        return Number(
-            this.searchContext.termSearch.hasBrowseEntry.split(',')[0]
-        );
-    }
-
     // Collect enough browse data to display previous, current, and
     // next heading.  This can mean fetching an additional page of data.
-    setPrevNext(take: number = 1): Promise<any> {
+    setPrevNext(take2: boolean = false): Promise<any> {
 
-        // Should never have to call this more than 3 times, with the
-        // first 2 collecting data and the final assing prev/next.
-        if (take > 3) { return Promise.resolve(); }
-
-        let pageEntry, previous: any;
+        let previous: any;
         const mbeId = this.pageEntryId();
 
-        this.staffCat.browsePagerResults.forEach(result => {
-            // ignore pivot and authority-only entries
-            if (!result.sources) { return; }
-
-            if (result.browse_entry === mbeId) {
-                pageEntry = result;
-            }
+        this.staffCat.browsePagerData.forEach(page => {
+            page.entries.forEach(entry => {
 
-            if (previous) {
-                if (result.browse_entry === mbeId) {
-                    this.prevEntry = previous;
+                if (previous) {
+                    if (entry.browse_entry === mbeId) {
+                        this.prevEntry = previous;
+                    }
+                    if (previous.browse_entry === mbeId) {
+                        this.nextEntry = entry;
+                    }
                 }
-                if (previous.browse_entry === mbeId) {
-                    this.nextEntry = result;
-                }
-            }
-            previous = result;
+                previous = entry;
+            });
         });
 
+        if (take2) {
+            // If we have to call this more than twice it means we've
+            // reached the boundary of the full data set and there's
+            // no more data to fetch.
+            return Promise.resolve();
+        }
+
         let promise;
-        if (!pageEntry) {
-            promise = this.fetchBrowseData(null)
 
-        } else if (!this.prevEntry) {
-            promise = this.fetchBrowseData(true);
+        if (!this.prevEntry) {
+            promise = this.fetchBrowsePage(true);
 
         } else if (!this.nextEntry) {
-            promise = this.fetchBrowseData(false);
+            promise = this.fetchBrowsePage(false);
         }
 
         if (promise) {
-            return promise.then(_ => this.setPrevNext(take + 1));
+            return promise.then(_ => this.setPrevNext(true));
         }
 
         return Promise.resolve();
     }
 
-    getBoundaryPivot(prev?: boolean): number {
-        const results = this.staffCat.browsePagerResults;
-        return prev ? results[0].pivot_point :
-            results[results.length - 1].pivot_point;
-    }
-
     setSearchPivot(prev?: boolean) {
         // When traversing browse result page boundaries, modify the
         // search pivot to keep up.
 
-        const mbeId = Number( // for this page
-            this.searchContext.termSearch.hasBrowseEntry.split(',')[0]);
-
         const targetMbe = Number(
-            prev ?  this.prevEntry.browse_entry : this.nextEntry.browse_entry
+            prev ? this.prevEntry.browse_entry : this.nextEntry.browse_entry
         );
 
-        let results = [].concat(this.staffCat.browsePagerResults);
-        if (prev) { results = results.reverse(); }
-
-        let current;
-        let lastPivot = null;
-        let lastWasPivot = false;
-        let done = false;
-        results.forEach(result => {
-            if (done) { return; }
-            const entryId = Number(result.browse_entry || -1);
-
-            // Pivots will be back-to-back in the full results set
-            // We only care about the first pivot encountered in a
-            // 2-pivot cluster.
-            if (result.pivot_point && !lastWasPivot) {
-                lastPivot = result.pivot_point;
-                lastWasPivot = true;
-                return;
-            }
+        const curPageIdx = this.getEntryPageIndex(this.pageEntryId());
+        const targetPageIdx = this.getEntryPageIndex(targetMbe);
 
-            lastWasPivot = false;
+        if (targetPageIdx !== curPageIdx) {
+            // We are crossing a page boundary
 
-            if (current) {
+            const curPage = this.getEntryPage(this.pageEntryId());
 
-                if (entryId === targetMbe && lastPivot) {
-                    this.searchContext.browseSearch.pivot = lastPivot;
-                    done = true;
-                }
-            } else if (entryId === mbeId) {
-                current = result;
+            if (prev) {
+                this.searchContext.browseSearch.pivot = curPage.leftPivot;
+
+            } else {
+                this.searchContext.browseSearch.pivot = curPage.rightPivot;
             }
-        });
+        }
     }
 
     // Find the browse entry for the next/prev page and navigate there
index b4eb0ad..34be7d5 100644 (file)
@@ -41,7 +41,7 @@ export class ResultsComponent implements OnInit, OnDestroy {
 
     ngOnInit() {
         this.searchContext = this.staffCat.searchContext;
-        this.staffCat.browsePagerResults = [];
+        this.staffCat.browsePagerData = [];
 
         // Our search context is initialized on page load.  Once
         // ResultsComponent is active, it will not be reinitialized,