]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/catalog/result/browse-pager.component.ts
LP1904788 Staff catalog browse results paging
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / result / browse-pager.component.ts
1 import {Component, OnInit, OnDestroy, Input} from '@angular/core';
2 import {Observable, Subscription} from 'rxjs';
3 import {tap, map, switchMap, distinctUntilChanged} from 'rxjs/operators';
4 import {CatalogService} from '@eg/share/catalog/catalog.service';
5 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
6 import {StaffCatalogService} from '../catalog.service';
7 import {IdlObject} from '@eg/core/idl.service';
8 import {BasketService} from '@eg/share/catalog/basket.service';
9
10 interface BrowsePage {
11     leftPivot: number;
12     rightPivot: number;
13     entries: any[];
14 }
15
16 @Component({
17   selector: 'eg-catalog-browse-pager',
18   templateUrl: 'browse-pager.component.html'
19 })
20 export class BrowsePagerComponent implements OnInit {
21
22     searchContext: CatalogSearchContext;
23     browseLoading = false;
24     prevEntry: any;
25     nextEntry: any;
26
27     constructor(
28         private cat: CatalogService,
29         private staffCat: StaffCatalogService
30     ) {}
31
32     ngOnInit() {
33         this.searchContext = this.staffCat.searchContext;
34         this.fetchPageData().then(_ => this.setPrevNext());
35     }
36
37     pageEntryId(): number {
38         return Number(
39             this.searchContext.termSearch.hasBrowseEntry.split(',')[0]
40         );
41     }
42
43     getEntryPageIndex(mbeId: number): number {
44         let idx = null;
45         this.staffCat.browsePagerData.forEach((page, index) => {
46             page.entries.forEach(entry => {
47                 if (entry.browse_entry === mbeId) {
48                     idx = index;
49                 }
50             });
51         });
52         return idx;
53     }
54
55
56     getEntryPage(mbeId: number): BrowsePage {
57         return this.staffCat.browsePagerData[this.getEntryPageIndex(mbeId)];
58     }
59
60     fetchPageData(): Promise<any> {
61
62         if (this.getEntryPage(this.pageEntryId())) {
63             // We have this page's data already
64             return Promise.resolve();
65         }
66
67         return this.fetchBrowsePage(null);
68     }
69
70     // Grab a page of browse results
71     fetchBrowsePage(prev: boolean): Promise<any> {
72         const ctx = this.searchContext.clone();
73         ctx.pager.limit = this.searchContext.pager.limit;
74         ctx.termSearch.hasBrowseEntry = null; // avoid term search
75
76         if (prev !== null) {
77             // Fetching data for a prev/next page which is not the
78             // current page.
79             const page = this.getEntryPage(this.pageEntryId());
80             const pivot = prev ? page.leftPivot : page.rightPivot;
81             if (pivot === null) {
82                 console.debug('Browse has reached the end of the rainbow');
83                 return;
84             }
85             ctx.browseSearch.pivot = pivot;
86         }
87
88         const results = [];
89         this.browseLoading = true;
90
91         return this.cat.browse(ctx)
92         .pipe(tap(result => results.push(result)))
93         .toPromise().then(_ => {
94             if (results.length === 0) { return; }
95
96             // At the end of the data set, final pivots are not present
97             let leftPivot = null;
98             let rightPivot = null;
99             if (results[0].pivot_point) {
100                 leftPivot = results.shift().pivot_point;
101             }
102             if (results[results.length - 1].pivot_point) {
103                rightPivot = results.pop().pivot_point;
104             }
105
106             // We only care about entries with bib record sources
107             let keepEntries = results.filter(e => Boolean(e.sources));
108
109             if (leftPivot === null || rightPivot === null) {
110                 // When you reach the edge of the data set, you can get
111                 // the same browse entries from different API calls.
112                 // From what I can tell, the last page will always have
113                 // a half page of entries, even if you've already seen some
114                 // of them in the previous page.  Trim the dupes since they
115                 // affect the logic.
116                 const keep = [];
117                 keepEntries.forEach(e => {
118                     if (!this.getEntryPage(e.browse_entry)) {
119                         keep.push(e);
120                     }
121                 });
122                 keepEntries = keep;
123             }
124
125             const page: BrowsePage = {
126                 leftPivot: leftPivot,
127                 rightPivot: rightPivot,
128                 entries: keepEntries
129             };
130
131             if (prev) {
132                 this.staffCat.browsePagerData.unshift(page);
133             } else {
134                 this.staffCat.browsePagerData.push(page);
135             }
136             this.browseLoading = false;
137         });
138     }
139
140     // Collect enough browse data to display previous, current, and
141     // next heading.  This can mean fetching an additional page of data.
142     setPrevNext(take2: boolean = false): Promise<any> {
143
144         let previous: any;
145         const mbeId = this.pageEntryId();
146
147         this.staffCat.browsePagerData.forEach(page => {
148             page.entries.forEach(entry => {
149
150                 if (previous) {
151                     if (entry.browse_entry === mbeId) {
152                         this.prevEntry = previous;
153                     }
154                     if (previous.browse_entry === mbeId) {
155                         this.nextEntry = entry;
156                     }
157                 }
158                 previous = entry;
159             });
160         });
161
162         if (take2) {
163             // If we have to call this more than twice it means we've
164             // reached the boundary of the full data set and there's
165             // no more data to fetch.
166             return Promise.resolve();
167         }
168
169         let promise;
170
171         if (!this.prevEntry) {
172             promise = this.fetchBrowsePage(true);
173
174         } else if (!this.nextEntry) {
175             promise = this.fetchBrowsePage(false);
176         }
177
178         if (promise) {
179             return promise.then(_ => this.setPrevNext(true));
180         }
181
182         return Promise.resolve();
183     }
184
185     setSearchPivot(prev?: boolean) {
186         // When traversing browse result page boundaries, modify the
187         // search pivot to keep up.
188
189         const targetMbe = Number(
190             prev ? this.prevEntry.browse_entry : this.nextEntry.browse_entry
191         );
192
193         const curPageIdx = this.getEntryPageIndex(this.pageEntryId());
194         const targetPageIdx = this.getEntryPageIndex(targetMbe);
195
196         if (targetPageIdx !== curPageIdx) {
197             // We are crossing a page boundary
198
199             const curPage = this.getEntryPage(this.pageEntryId());
200
201             if (prev) {
202                 this.searchContext.browseSearch.pivot = curPage.leftPivot;
203
204             } else {
205                 this.searchContext.browseSearch.pivot = curPage.rightPivot;
206             }
207         }
208     }
209
210     // Find the browse entry for the next/prev page and navigate there
211     // if possible.  Returns false if not enough data is available.
212     goToBrowsePage(prev: boolean): boolean {
213         const ctx = this.searchContext;
214         const target = prev ? this.prevEntry : this.nextEntry;
215
216         if (!target) { return false; }
217
218         this.setSearchPivot(prev);
219
220         // Jump to the selected browse entry's page.
221         ctx.termSearch.hasBrowseEntry = target.browse_entry + ',' + target.fields;
222         ctx.pager.offset = 0; // this is a new records-for-browse-entry search
223         this.staffCat.search();
224
225         return true;
226     }
227 }
228
229