]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts
LP1850473: manual and automated eslint fixes
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / catalog / record / record.component.ts
1 import {Component, OnInit, ViewChild, HostListener} from '@angular/core';
2 import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
3 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
4 import {PcrudService} from '@eg/core/pcrud.service';
5 import {IdlObject} from '@eg/core/idl.service';
6 import {AuthService} from '@eg/core/auth.service';
7 import {CatalogSearchContext} from '@eg/share/catalog/search-context';
8 import {CatalogService} from '@eg/share/catalog/catalog.service';
9 import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
10 import {StaffCatalogService} from '../catalog.service';
11 import {AddedContentComponent} from '@eg/staff/catalog/content/added-content.component';
12 import {StoreService} from '@eg/core/store.service';
13 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
14 import {MarcEditorComponent} from '@eg/staff/share/marc-edit/editor.component';
15 import {HoldingsMaintenanceComponent} from './holdings.component';
16 import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
17 import { ServerStoreService } from '@eg/core/server-store.service';
18
19 @Component({
20     selector: 'eg-catalog-record',
21     templateUrl: 'record.component.html',
22     styleUrls: ['record.component.css']
23 })
24 export class RecordComponent implements OnInit {
25
26     recordId: number;
27     recordTab: string;
28     added_content_activated = false;
29     added_content_sources: string[] = [];
30     summary: BibRecordSummary;
31     searchContext: CatalogSearchContext;
32     @ViewChild('recordTabs', { static: true }) recordTabs: NgbNav;
33     @ViewChild('marcEditor', {static: false}) marcEditor: MarcEditorComponent;
34     @ViewChild('addedContent', { static: true }) addedContent: AddedContentComponent;
35
36     @ViewChild('holdingsMaint', {static: false})
37         holdingsMaint: HoldingsMaintenanceComponent;
38
39     defaultTab: string; // eg.cat.default_record_tab
40
41     @ViewChild('pendingChangesDialog', {static: false})
42         pendingChangesDialog: ConfirmDialogComponent;
43
44     constructor(
45         private router: Router,
46         private route: ActivatedRoute,
47         private pcrud: PcrudService,
48         private auth: AuthService,
49         private bib: BibRecordService,
50         private cat: CatalogService,
51         private staffCat: StaffCatalogService,
52         private holdings: HoldingsService,
53         private store: StoreService,
54         private serverStore: ServerStoreService,
55     ) {}
56
57     ngOnInit() {
58         this.searchContext = this.staffCat.searchContext;
59
60         this.defaultTab =
61             this.store.getLocalItem('eg.cat.default_record_tab')
62             || 'item_table';
63
64         // Watch for URL record ID changes
65         // This includes the initial route.
66         // When applying the default configured tab, no navigation occurs
67         // to apply the tab name to the URL, it displays as the default.
68         // This is done so no intermediate redirect is required, which
69         // messes with browser back/forward navigation.
70         this.route.paramMap.subscribe((params: ParamMap) => {
71             this.recordTab = params.get('tab');
72             this.recordId = +params.get('id');
73             this.searchContext = this.staffCat.searchContext;
74
75             this.store.setLocalItem('eg.cat.last_record_retrieved', this.recordId);
76
77             if (!this.recordTab) {
78                 this.recordTab = this.defaultTab || 'item_table';
79             }
80
81             this.loadRecord();
82         });
83     }
84
85     setDefaultTab() {
86         this.defaultTab = this.recordTab;
87         this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
88     }
89
90     // Changing a tab in the UI means changing the route.
91     // Changing the route ultimately results in changing the tab.
92     beforeNavChange(evt: NgbNavChangeEvent) {
93
94         // prevent tab changing until after route navigation
95         evt.preventDefault();
96
97         // Protect against tab changes with dirty data.
98         this.canDeactivate().then(ok => {
99             if (ok) {
100                 this.recordTab = evt.nextId;
101                 this.routeToTab();
102             }
103         });
104     }
105
106     /*
107      * Handle 3 types of navigation which can cause loss of data.
108      * 1. Record detail tab navigation (see also beforeTabChange())
109      * 2. Intra-Angular route navigation away from the record detail page
110      * 3. Browser page unload/reload
111      *
112      * For the #1, and #2, display a eg confirmation dialog.
113      * For #3 use the stock browser onbeforeunload dialog.
114      *
115      * Note in this case a tab change is a route change, but it's one
116      * which does not cause RecordComponent to unload, so it has to be
117      * manually tracked in beforeTabChange().
118      */
119     @HostListener('window:beforeunload', ['$event'])
120     canDeactivate($event?: Event): Promise<boolean> {
121
122         if (this.marcEditor && this.marcEditor.changesPending()) {
123
124             // Each warning dialog clears the current "changes are pending"
125             // flag so the user is not presented with the dialog again
126             // unless new changes are made.
127             this.marcEditor.clearPendingChanges();
128
129             if ($event) { // window.onbeforeunload
130                 $event.preventDefault();
131                 $event.returnValue = true;
132
133             } else { // tab OR route change.
134                 return this.pendingChangesDialog.open().toPromise();
135             }
136
137         } else {
138             return Promise.resolve(true);
139         }
140     }
141
142     routeToTab() {
143         const url =
144             `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
145
146         // Retain search parameters
147         this.router.navigate([url], {queryParamsHandling: 'merge'});
148     }
149
150     loadRecord(): void {
151
152         // Avoid re-fetching the same record summary during tab navigation.
153         if (this.staffCat.currentDetailRecordSummary &&
154             this.recordId === this.staffCat.currentDetailRecordSummary.id) {
155             this.summary = this.staffCat.currentDetailRecordSummary;
156             this.activateAddedContent();
157             return;
158         }
159
160         this.summary = null;
161         this.bib.getBibSummary(
162             this.recordId,
163             this.searchContext.searchOrg.id(),
164             this.searchContext.isStaff).toPromise()
165             .then(summary => {
166                 this.summary =
167                 this.staffCat.currentDetailRecordSummary = summary;
168                 this.activateAddedContent();
169             });
170     }
171
172     // Lets us intercept the summary object and augment it with
173     // search highlight data if/when it becomes available from
174     // an externally executed search.
175     summaryForDisplay(): BibRecordSummary {
176         if (!this.summary) { return null; }
177         const sum = this.summary;
178         const ctx = this.searchContext;
179
180         if (Object.keys(sum.displayHighlights).length === 0) {
181             if (ctx.highlightData[sum.id]) {
182                 sum.displayHighlights = ctx.highlightData[sum.id];
183             }
184         }
185
186         return this.summary;
187     }
188
189     currentSearchOrg(): IdlObject {
190         if (this.staffCat && this.staffCat.searchContext) {
191             return this.staffCat.searchContext.searchOrg;
192         }
193         return null;
194     }
195
196     handleMarcRecordSaved() {
197         this.staffCat.currentDetailRecordSummary = null;
198         this.loadRecord();
199     }
200
201     // Our actions component broadcast a request to add holdings.
202     // If our Holdings Maintenance component is active/visible, ask
203     // it to figure out what data to pass to the holdings editor.
204     // Otherwise, just tell it to create a new call number and
205     // copy at the current working location.
206     addHoldingsRequested() {
207         if (this.holdingsMaint && this.holdingsMaint.holdingsGrid) {
208             this.holdingsMaint.openHoldingAdd(
209                 this.holdingsMaint.holdingsGrid.context.getSelectedRows(),
210                 true, true
211             );
212
213         } else {
214
215             this.holdings.spawnAddHoldingsUi(
216                 this.recordId, null, [{owner: this.auth.user().ws_ou()}]);
217         }
218     }
219
220     // This just sets the record component-level flag. choices about /if/ to
221     // gather AC should go in here and set this.added_content_activated as needed.
222     activateAddedContent(): void {
223         // NovelistSelect settings
224         this.serverStore.getItemBatch([
225             'staff.added_content.novelistselect.profile',
226             'staff.added_content.novelistselect.passwd'
227         ]).then(settings => {
228             // eslint-disable-next-line eqeqeq
229             const activate = !!(Object.values(settings).filter(v => !!v).length == 2);
230             this.added_content_activated ||= activate;
231
232             if (activate) {
233                 if (!this.added_content_sources.includes('novelist')) {
234                     this.added_content_sources.push('novelist');
235                 }
236             }
237         });
238     }
239 }
240
241