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