]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor.component.ts
LP1852782 MARC editor prevent navigation with changes
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / editor.component.ts
1 import {Component, Input, Output, OnInit, EventEmitter, ViewChild} from '@angular/core';
2 import {IdlService} from '@eg/core/idl.service';
3 import {EventService} from '@eg/core/event.service';
4 import {NetService} from '@eg/core/net.service';
5 import {AuthService} from '@eg/core/auth.service';
6 import {OrgService} from '@eg/core/org.service';
7 import {PcrudService} from '@eg/core/pcrud.service';
8 import {ToastService} from '@eg/share/toast/toast.service';
9 import {ServerStoreService} from '@eg/core/server-store.service';
10 import {StringComponent} from '@eg/share/string/string.component';
11 import {MarcRecord} from './marcrecord';
12 import {ComboboxEntry, ComboboxComponent
13   } from '@eg/share/combobox/combobox.component';
14 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
15 import {MarcEditContext} from './editor-context';
16 import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
17
18 interface MarcSavedEvent {
19     marcXml: string;
20     bibSource?: number;
21 }
22
23 /**
24  * MARC Record editor main interface.
25  */
26
27 @Component({
28   selector: 'eg-marc-editor',
29   templateUrl: './editor.component.html'
30 })
31
32 export class MarcEditorComponent implements OnInit {
33
34     editorTab: 'rich' | 'flat';
35     sources: ComboboxEntry[];
36     context: MarcEditContext;
37
38     @Input() recordType: 'biblio' | 'authority' = 'biblio';
39
40     @Input() set recordId(id: number) {
41         if (!id) { return; }
42         if (this.record && this.record.id === id) { return; }
43         this.fromId(id);
44     }
45
46     @Input() set recordXml(xml: string) {
47         if (xml) {
48             this.fromXml(xml);
49         }
50     }
51
52     get record(): MarcRecord {
53         return this.context.record;
54     }
55
56     // Tell us which record source to select by default.
57     // Useful for new records and in-place editing from bare XML.
58     @Input() recordSource: number;
59
60     // If true, saving records to the database is assumed to
61     // happen externally.  IOW, the record editor is just an
62     // in-place MARC modification interface.
63     @Input() inPlaceMode: boolean;
64
65     // In inPlaceMode, this is emitted in lieu of saving the record
66     // in th database.  When inPlaceMode is false, this is emitted after
67     // the record is successfully saved.
68     @Output() recordSaved: EventEmitter<MarcSavedEvent>;
69
70     @ViewChild('sourceSelector', { static: true }) sourceSelector: ComboboxComponent;
71     @ViewChild('confirmDelete', { static: true }) confirmDelete: ConfirmDialogComponent;
72     @ViewChild('confirmUndelete', { static: true }) confirmUndelete: ConfirmDialogComponent;
73     @ViewChild('cannotDelete', { static: true }) cannotDelete: ConfirmDialogComponent;
74     @ViewChild('successMsg', { static: true }) successMsg: StringComponent;
75     @ViewChild('failMsg', { static: true }) failMsg: StringComponent;
76
77     constructor(
78         private evt: EventService,
79         private idl: IdlService,
80         private net: NetService,
81         private auth: AuthService,
82         private org: OrgService,
83         private pcrud: PcrudService,
84         private toast: ToastService,
85         private store: ServerStoreService
86     ) {
87         this.sources = [];
88         this.recordSaved = new EventEmitter<MarcSavedEvent>();
89         this.context = new MarcEditContext();
90     }
91
92     ngOnInit() {
93
94         this.context.recordType = this.recordType;
95
96         this.store.getItem('cat.marcedit.flateditor').then(
97             useFlat => this.editorTab = useFlat ? 'flat' : 'rich');
98
99         this.pcrud.retrieveAll('cbs').subscribe(
100             src => this.sources.push({id: +src.id(), label: src.source()}),
101             _ => {},
102             () => {
103                 this.sources = this.sources.sort((a, b) =>
104                     a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1
105                 );
106
107                 if (this.recordSource) {
108                     this.sourceSelector.applyEntryId(this.recordSource);
109                 }
110             }
111         );
112     }
113
114     changesPending(): boolean {
115         return this.context.changesPending;
116     }
117
118     clearPendingChanges() {
119         this.context.changesPending = false;
120     }
121
122     // Remember the last used tab as the preferred tab.
123     tabChange(evt: NgbTabChangeEvent) {
124
125         // Avoid undo persistence across tabs since that could result
126         // in changes getting lost.
127         this.context.resetUndos();
128
129         if (evt.nextId === 'flat') {
130             this.store.setItem('cat.marcedit.flateditor', true);
131         } else {
132             this.store.removeItem('cat.marcedit.flateditor');
133         }
134     }
135
136     saveRecord(): Promise<any> {
137         const xml = this.record.toXml();
138
139         // Save actions clears any pending changes.
140         this.context.changesPending = false;
141         this.context.resetUndos();
142
143         let sourceName: string = null;
144         let sourceId: number = null;
145
146         if (this.sourceSelector.selected) {
147             sourceName = this.sourceSelector.selected.label;
148             sourceId = this.sourceSelector.selected.id;
149         }
150
151         if (this.inPlaceMode) {
152             // Let the caller have the modified XML and move on.
153             this.recordSaved.emit({marcXml: xml, bibSource: sourceId});
154             return Promise.resolve();
155         }
156
157         if (this.record.id) { // Editing an existing record
158
159             const method = 'open-ils.cat.biblio.record.xml.update';
160
161             return this.net.request('open-ils.cat', method,
162                 this.auth.token(), this.record.id, xml, sourceName
163             ).toPromise().then(response => {
164
165                 const evt = this.evt.parse(response);
166                 if (evt) {
167                     console.error(evt);
168                     this.failMsg.current().then(msg => this.toast.warning(msg));
169                     return;
170                 }
171
172                 this.successMsg.current().then(msg => this.toast.success(msg));
173                 this.recordSaved.emit({marcXml: xml, bibSource: sourceId});
174                 return response;
175             });
176
177         } else {
178             // TODO: create a new record
179         }
180     }
181
182     fromId(id: number): Promise<any> {
183         return this.pcrud.retrieve('bre', id)
184         .toPromise().then(bib => {
185             this.context.record = new MarcRecord(bib.marc());
186             this.record.id = id;
187             this.record.deleted = bib.deleted() === 't';
188             if (bib.source()) {
189                 this.sourceSelector.applyEntryId(+bib.source());
190             }
191         });
192     }
193
194     fromXml(xml: string) {
195         this.context.record = new MarcRecord(xml);
196         this.record.id = null;
197     }
198
199     deleteRecord(): Promise<any> {
200
201         return this.confirmDelete.open().toPromise()
202         .then(yes => {
203             if (!yes) { return; }
204
205             return this.net.request('open-ils.cat',
206                 'open-ils.cat.biblio.record_entry.delete',
207                 this.auth.token(), this.record.id).toPromise()
208
209             .then(resp => {
210
211                 const evt = this.evt.parse(resp);
212                 if (evt) {
213                     if (evt.textcode === 'RECORD_NOT_EMPTY') {
214                         return this.cannotDelete.open().toPromise();
215                     } else {
216                         console.error(evt);
217                         return alert(evt);
218                     }
219                 }
220                 return this.fromId(this.record.id)
221                 .then(_ => this.recordSaved.emit(
222                     {marcXml: this.record.toXml()}));
223             });
224         });
225     }
226
227     undeleteRecord(): Promise<any> {
228
229         return this.confirmUndelete.open().toPromise()
230         .then(yes => {
231             if (!yes) { return; }
232
233             return this.net.request('open-ils.cat',
234                 'open-ils.cat.biblio.record_entry.undelete',
235                 this.auth.token(), this.record.id).toPromise()
236
237             .then(resp => {
238
239                 const evt = this.evt.parse(resp);
240                 if (evt) { console.error(evt); return alert(evt); }
241
242                 return this.fromId(this.record.id)
243                 .then(_ => this.recordSaved.emit(
244                     {marcXml: this.record.toXml()}));
245             });
246         });
247     }
248 }
249