]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/catalog/search-templates.component.ts
LP1904036 Login session keys expire; hold patron uses login key
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / search-templates.component.ts
1 import {Component, OnInit, Input, ViewChild} from '@angular/core';
2 import {OrgService} from '@eg/core/org.service';
3 import {StoreService} from '@eg/core/store.service';
4 import {ServerStoreService} from '@eg/core/server-store.service';
5 import {PcrudService} from '@eg/core/pcrud.service';
6 import {DialogComponent} from '@eg/share/dialog/dialog.component';
7 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
8 import {StringService} from '@eg/share/string/string.service';
9 import {CatalogService} from '@eg/share/catalog/catalog.service';
10 import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
11 import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
12 import {StaffCatalogService} from './catalog.service';
13 import {AnonCacheService} from '@eg/share/util/anon-cache.service';
14 import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
15
16 const SAVED_TEMPLATES_SETTING = 'eg.catalog.search_templates';
17 const RECENT_SEARCHES_KEY = 'eg.catalog.recent_searches';
18
19 class SearchTemplate {
20     name: string;
21     params: any = {}; // routerLink-compatible URL params object
22     addTime?: number;
23     constructor(name: string, params: any) {
24         this.name = name;
25         this.params = params;
26     }
27 }
28
29 @Component({
30   selector: 'eg-catalog-search-templates',
31   templateUrl: 'search-templates.component.html'
32 })
33 export class SearchTemplatesComponent extends DialogComponent implements OnInit {
34
35     recentSearchesCount = 0;
36     context: CatalogSearchContext;
37     templates: SearchTemplate[] = [];
38     searches: SearchTemplate[] = [];
39     searchesCacheKey: string;
40     templateName: string;
41
42     @Input() searchTab: string;
43
44     @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
45     @ViewChild('confirmDeleteAll', { static: true }) confirmDeleteAll: ConfirmDialogComponent;
46     @ViewChild('confirmDeleteSearches', { static: true }) confirmDeleteSearches: ConfirmDialogComponent;
47
48     constructor(
49         private org: OrgService,
50         private store: StoreService,             // anon cache key
51         private serverStore: ServerStoreService, // search templates
52         private cache: AnonCacheService,         // recent searches
53         private strings: StringService,
54         private cat: CatalogService,
55         private catUrl: CatalogUrlService,
56         private staffCat: StaffCatalogService,
57         private modal: NgbModal) {
58         super(modal);
59     }
60
61     ngOnInit() {
62         this.context = this.staffCat.searchContext;
63
64         this.serverStore.getItem('opac.staff_saved_search.size')
65         .then(size => {
66             if (!size) { return; }
67
68             this.recentSearchesCount = Number(size);
69
70             this.getSearches().then(_ => {
71                 this.searches.forEach(
72                     s => s.params.ridx = ++this.staffCat.routeIndex);
73
74                 // Save the search that runs on page load.
75                 this.saveSearch(this.context);
76                 // Watch for new searches
77                 this.cat.onSearchComplete.subscribe(ctx => this.saveSearch(ctx));
78             });
79         });
80
81         this.getTemplates();
82     }
83
84     selectedTemplate(): string {
85         return this.staffCat.selectedTemplate;
86     }
87
88     getSearches(): Promise<any> {
89         this.searches = [];
90
91         if (this.searchesCacheKey) {
92             // We've already started saving searches in the current instance.
93
94             return this.cache.getItem(this.searchesCacheKey, 'searches')
95                 .then(searches => this.searches = searches || []);
96         }
97
98         const cacheKey = this.store.getLoginSessionItem(RECENT_SEARCHES_KEY);
99
100         if (cacheKey) {
101             // We have a saved search key, see if we have any searches.
102
103             this.searchesCacheKey = cacheKey;
104             return this.cache.getItem(this.searchesCacheKey, 'searches')
105                 .then(searches => this.searches = searches || []);
106
107         } else {
108             // No saved searches in progress.  Start from scratch.
109
110             return this.cache.setItem(null, 'searches', []) // generates cache key
111             .then(cKey => {
112                 this.searchesCacheKey = cKey;
113                 this.store.setLoginSessionItem(RECENT_SEARCHES_KEY, cKey);
114             });
115         }
116     }
117
118     searchSelected(search: SearchTemplate) {
119         // increment the router index in case the template is used
120         // twice in a row.
121         search.params.ridx = ++this.staffCat.routeIndex;
122     }
123
124     // Returns searches most recent first
125     sortSearches(): SearchTemplate[] {
126         return this.searches.sort((a, b) => a.addTime > b.addTime ? -1 : 1);
127     }
128
129     deleteSearches() {
130         this.confirmDeleteSearches.open().subscribe(yes => {
131             if (!yes) { return; }
132             this.searches = [];
133             this.cache.setItem(this.searchesCacheKey, 'searches', []);
134         });
135     }
136
137     getSearchPath(search: SearchTemplate): string {
138         return search.params.searchTab === 'browse' ?
139             '/staff/catalog/browse' : '/staff/catalog/search';
140     }
141
142     saveSearch(context: CatalogSearchContext) {
143
144         let matchFound = false;
145         this.searches.forEach(sch => {
146             const tmpCtx = this.catUrl.fromUrlHash(sch.params);
147             if (tmpCtx.equals(context)) {
148                 matchFound = true;
149             }
150         });
151
152         if (matchFound) { return; }
153
154         let query: string;
155         switch (this.searchTab) {
156             case 'term':
157                 query = context.termSearch.query[0];
158                 break;
159             case 'marc':
160                 query = context.marcSearch.values[0];
161                 break;
162             case 'ident':
163                 query = context.identSearch.value;
164                 break;
165             case 'browse':
166                 query = context.browseSearch.value;
167                 break;
168             case 'cnbrowse':
169                 query = context.cnBrowseSearch.value;
170                 break;
171         }
172
173         if (!query) {
174             // no query means nothing was searchable.
175             return;
176         }
177
178         this.strings.interpolate(
179             'eg.catalog.recent_search.label',
180             {query: query, tab: this.searchTab}
181
182         ).then(txt => {
183
184             const urlParams = this.prepareSearch(context);
185             const search = new SearchTemplate(txt, urlParams);
186             search.addTime = new Date().getTime();
187
188             this.searches.unshift(search);
189
190             if (this.searches.length > this.recentSearchesCount) {
191                 // this bit of magic will lop off the end of the array.
192                 this.searches.length = this.recentSearchesCount;
193             }
194
195             this.cache.setItem(
196                 this.searchesCacheKey, 'searches', this.searches)
197             .then(_ => search.params.ridx = ++this.staffCat.routeIndex);
198         });
199     }
200
201     getTemplates(): Promise<any> {
202         this.templates = [];
203
204         return this.serverStore.getItem(SAVED_TEMPLATES_SETTING).then(
205             templates => {
206                 if (templates && templates.length) {
207                     this.templates = templates;
208
209                     // route index required to force the route to take
210                     // effect.  See ./catalog.service.ts
211                     this.templates.forEach(tmpl =>
212                         tmpl.params.ridx = ++this.staffCat.routeIndex);
213                 }
214             }
215         );
216     }
217
218     sortTemplates(): SearchTemplate[] {
219         return this.templates.sort((a, b) =>
220             a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
221     }
222
223     templateSelected(tmpl: SearchTemplate) {
224         this.staffCat.selectedTemplate = tmpl.name;
225         // increment the router index in case the template is used
226         // twice in a row.
227         tmpl.params.ridx = ++this.staffCat.routeIndex;
228         console.log('selected template = ', this.staffCat.selectedTemplate);
229     }
230
231     // Adds dummy query content to the context object so the
232     // CatalogUrlService will recognize the content as searchable
233     // and therefor URL-encodable.
234     addDummyQueries(context: CatalogSearchContext) {
235         context.termSearch.query = context.termSearch.query.map(q => 'x');
236         context.marcSearch.values = context.marcSearch.values.map(q => 'x');
237         context.browseSearch.value = 'x';
238         context.identSearch.value = 'x';
239     }
240
241     // Remove the dummy query content before saving the search template.
242     removeDummyQueries(urlParams: any) {
243
244         if (Array.isArray(urlParams.query)) {
245             const arr = urlParams.query as Array<string>;
246             urlParams.query = arr.map(q => '');
247         } else {
248             urlParams.query = '';
249         }
250
251         if (Array.isArray(urlParams.marcValue)) {
252             const arr = urlParams.marcValue as Array<string>;
253             urlParams.marcValue = arr.map(q => '');
254         } else {
255             urlParams.marcValue = '';
256         }
257
258         urlParams.identQuery = '';
259         urlParams.browseTerm = '';
260     }
261
262     // Prepares a save-able URL params hash from the current context.
263     prepareSearch(ctx: CatalogSearchContext,
264         withDummyData?: boolean): {[key: string]: string | string[]} {
265
266         const context = ctx.clone();
267
268         if (withDummyData) {
269             this.addDummyQueries(context);
270         }
271
272         context.scrub(this.searchTab);
273
274         const urlParams = this.catUrl.toUrlParams(context);
275
276         if (withDummyData) {
277             this.removeDummyQueries(urlParams);
278         }
279
280         // Some data should not go into the template.
281         delete urlParams.org;
282         delete urlParams.ridx;
283
284         urlParams.searchTab = this.searchTab;
285
286         return urlParams;
287     }
288
289     saveTemplate(): Promise<any> {
290         if (!this.templateName) { return Promise.resolve(); }
291
292         this.staffCat.selectedTemplate = this.templateName;
293
294         const urlParams = this.prepareSearch(this.context, true);
295
296         this.templates.push(
297             new SearchTemplate(this.templateName, urlParams));
298
299         return this.applyTemplateChanges().then(_ => this.close());
300     }
301
302     applyTemplateChanges(): Promise<any> {
303         return this.serverStore.setItem(SAVED_TEMPLATES_SETTING, this.templates);
304     }
305
306     deleteTemplate() {
307         this.confirmDelete.open().subscribe(yes => {
308             if (!yes) { return; }
309
310             const templates: SearchTemplate[] = [];
311             this.templates.forEach(tmpl => {
312                 if (tmpl.name !== this.staffCat.selectedTemplate) {
313                     templates.push(tmpl);
314                 }
315             });
316
317             this.templates = templates;
318             this.staffCat.selectedTemplate = '';
319             this.applyTemplateChanges();
320         });
321     }
322
323     deleteAllTemplates() {
324         this.confirmDeleteAll.open().subscribe(yes => {
325             if (!yes) { return; }
326             this.templates = [];
327             this.staffCat.selectedTemplate = '';
328             this.applyTemplateChanges();
329         });
330     }
331 }
332
333