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';
18 interface MarcSavedEvent {
24 * MARC Record editor main interface.
28 selector: 'eg-marc-editor',
29 templateUrl: './editor.component.html'
32 export class MarcEditorComponent implements OnInit {
34 editorTab: 'rich' | 'flat';
35 sources: ComboboxEntry[];
36 context: MarcEditContext;
38 @Input() recordType: 'biblio' | 'authority' = 'biblio';
40 @Input() set recordId(id: number) {
42 if (this.record && this.record.id === id) { return; }
46 @Input() set recordXml(xml: string) {
52 get record(): MarcRecord {
53 return this.context.record;
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;
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;
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>;
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;
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
88 this.recordSaved = new EventEmitter<MarcSavedEvent>();
89 this.context = new MarcEditContext();
94 this.context.recordType = this.recordType;
96 this.store.getItem('cat.marcedit.flateditor').then(
97 useFlat => this.editorTab = useFlat ? 'flat' : 'rich');
99 this.pcrud.retrieveAll('cbs').subscribe(
100 src => this.sources.push({id: +src.id(), label: src.source()}),
103 this.sources = this.sources.sort((a, b) =>
104 a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1
107 if (this.recordSource) {
108 this.sourceSelector.applyEntryId(this.recordSource);
114 changesPending(): boolean {
115 return this.context.changesPending;
118 clearPendingChanges() {
119 this.context.changesPending = false;
122 // Remember the last used tab as the preferred tab.
123 tabChange(evt: NgbTabChangeEvent) {
125 // Avoid undo persistence across tabs since that could result
126 // in changes getting lost.
127 this.context.resetUndos();
129 if (evt.nextId === 'flat') {
130 this.store.setItem('cat.marcedit.flateditor', true);
132 this.store.removeItem('cat.marcedit.flateditor');
136 saveRecord(): Promise<any> {
137 const xml = this.record.toXml();
139 // Save actions clears any pending changes.
140 this.context.changesPending = false;
141 this.context.resetUndos();
143 let sourceName: string = null;
144 let sourceId: number = null;
146 if (this.sourceSelector.selected) {
147 sourceName = this.sourceSelector.selected.label;
148 sourceId = this.sourceSelector.selected.id;
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();
157 if (this.record.id) { // Editing an existing record
159 const method = 'open-ils.cat.biblio.record.xml.update';
161 return this.net.request('open-ils.cat', method,
162 this.auth.token(), this.record.id, xml, sourceName
163 ).toPromise().then(response => {
165 const evt = this.evt.parse(response);
168 this.failMsg.current().then(msg => this.toast.warning(msg));
172 this.successMsg.current().then(msg => this.toast.success(msg));
173 this.recordSaved.emit({marcXml: xml, bibSource: sourceId});
178 // TODO: create a new record
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());
187 this.record.deleted = bib.deleted() === 't';
189 this.sourceSelector.applyEntryId(+bib.source());
194 fromXml(xml: string) {
195 this.context.record = new MarcRecord(xml);
196 this.record.id = null;
199 deleteRecord(): Promise<any> {
201 return this.confirmDelete.open().toPromise()
203 if (!yes) { return; }
205 return this.net.request('open-ils.cat',
206 'open-ils.cat.biblio.record_entry.delete',
207 this.auth.token(), this.record.id).toPromise()
211 const evt = this.evt.parse(resp);
213 if (evt.textcode === 'RECORD_NOT_EMPTY') {
214 return this.cannotDelete.open().toPromise();
220 return this.fromId(this.record.id)
221 .then(_ => this.recordSaved.emit(
222 {marcXml: this.record.toXml()}));
227 undeleteRecord(): Promise<any> {
229 return this.confirmUndelete.open().toPromise()
231 if (!yes) { return; }
233 return this.net.request('open-ils.cat',
234 'open-ils.cat.biblio.record_entry.undelete',
235 this.auth.token(), this.record.id).toPromise()
239 const evt = this.evt.parse(resp);
240 if (evt) { console.error(evt); return alert(evt); }
242 return this.fromId(this.record.id)
243 .then(_ => this.recordSaved.emit(
244 {marcXml: this.record.toXml()}));