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';
20 selector: 'eg-catalog-record',
21 templateUrl: 'record.component.html',
22 styleUrls: ['record.component.css']
24 export class RecordComponent implements OnInit {
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;
36 @ViewChild('holdingsMaint', {static: false})
37 holdingsMaint: HoldingsMaintenanceComponent;
39 defaultTab: string; // eg.cat.default_record_tab
41 @ViewChild('pendingChangesDialog', {static: false})
42 pendingChangesDialog: ConfirmDialogComponent;
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,
58 this.searchContext = this.staffCat.searchContext;
61 this.store.getLocalItem('eg.cat.default_record_tab')
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;
75 this.store.setLocalItem('eg.cat.last_record_retrieved', this.recordId);
77 if (!this.recordTab) {
78 this.recordTab = this.defaultTab || 'item_table';
86 this.defaultTab = this.recordTab;
87 this.store.setLocalItem('eg.cat.default_record_tab', this.recordTab);
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) {
94 // prevent tab changing until after route navigation
97 // Protect against tab changes with dirty data.
98 this.canDeactivate().then(ok => {
100 this.recordTab = evt.nextId;
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
112 * For the #1, and #2, display a eg confirmation dialog.
113 * For #3 use the stock browser onbeforeunload dialog.
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().
119 @HostListener('window:beforeunload', ['$event'])
120 canDeactivate($event?: Event): Promise<boolean> {
122 if (this.marcEditor && this.marcEditor.changesPending()) {
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();
129 if ($event) { // window.onbeforeunload
130 $event.preventDefault();
131 $event.returnValue = true;
133 } else { // tab OR route change.
134 return this.pendingChangesDialog.open().toPromise();
138 return Promise.resolve(true);
144 `/staff/catalog/record/${this.recordId}/${this.recordTab}`;
146 // Retain search parameters
147 this.router.navigate([url], {queryParamsHandling: 'merge'});
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();
161 this.bib.getBibSummary(
163 this.searchContext.searchOrg.id(),
164 this.searchContext.isStaff).toPromise()
167 this.staffCat.currentDetailRecordSummary = summary;
168 this.activateAddedContent();
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;
180 if (Object.keys(sum.displayHighlights).length === 0) {
181 if (ctx.highlightData[sum.id]) {
182 sum.displayHighlights = ctx.highlightData[sum.id];
189 currentSearchOrg(): IdlObject {
190 if (this.staffCat && this.staffCat.searchContext) {
191 return this.staffCat.searchContext.searchOrg;
196 handleMarcRecordSaved() {
197 this.staffCat.currentDetailRecordSummary = null;
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(),
215 this.holdings.spawnAddHoldingsUi(
216 this.recordId, null, [{owner: this.auth.user().ws_ou()}]);
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;
233 if (!this.added_content_sources.includes('novelist')) {
234 this.added_content_sources.push('novelist');