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