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