1 import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter,
2 ViewChild, OnDestroy} from '@angular/core';
3 import {filter} from 'rxjs/operators';
4 import {IdlService} from '@eg/core/idl.service';
5 import {NetService} from '@eg/core/net.service';
6 import {OrgService} from '@eg/core/org.service';
7 import {ServerStoreService} from '@eg/core/server-store.service';
8 import {TagTableService} from './tagtable.service';
9 import {MarcRecord, MarcField} from './marcrecord';
10 import {MarcEditContext} from './editor-context';
11 import {AuthorityLinkingDialogComponent} from './authority-linking-dialog.component';
12 import {PhysCharDialogComponent} from './phys-char-dialog.component';
16 * MARC Record rich editor interface.
20 selector: 'eg-marc-rich-editor',
21 templateUrl: './rich-editor.component.html',
22 styleUrls: ['rich-editor.component.css']
25 export class MarcRichEditorComponent implements OnInit {
27 @Input() context: MarcEditContext;
28 get record(): MarcRecord { return this.context.record; }
32 randId = Math.floor(Math.random() * 100000);
33 stackSubfields: boolean;
34 controlledBibTags: string[] = [];
36 @ViewChild('authLinker', {static: false})
37 authLinker: AuthorityLinkingDialogComponent;
39 @ViewChild('physCharDialog', {static: false})
40 physCharDialog: PhysCharDialogComponent;
43 private idl: IdlService,
44 private net: NetService,
45 private org: OrgService,
46 private store: ServerStoreService,
47 private tagTable: TagTableService
52 this.store.getItem('cat.marcedit.stack_subfields')
53 .then(stack => this.stackSubfields = stack);
56 this.context.recordChange.subscribe(__ => this.init()));
58 // Changing the Type fixed field means loading new meta-metadata.
59 this.record.fixedFieldChange.pipe(filter(code => code === 'Type'))
60 .subscribe(_ => this.init());
63 init(): Promise<any> {
64 this.dataLoaded = false;
66 if (!this.record) { return Promise.resolve(); }
69 this.tagTable.loadTags({
70 marcRecordType: this.context.recordType,
71 ffType: this.record.recordType()
72 }).then(table => this.context.tagTable = table),
73 this.tagTable.getControlledBibTags().then(
74 tags => this.controlledBibTags = tags),
77 // setTimeout forces all of our sub-components to rerender
78 // themselves each time init() is called. Without this,
79 // changing the record Type would only re-render the fixed
80 // fields editor when data had to be fetched from the
81 // network. (Sometimes the data is cached).
82 setTimeout(() => this.dataLoaded = true)
86 fetchSettings(): Promise<any> {
87 // Fetch at rich editor load time to cache.
88 return this.org.settings(['cat.marc_control_number_identifier']);
91 stackSubfieldsChange() {
92 if (this.stackSubfields) {
93 this.store.setItem('cat.marcedit.stack_subfields', true);
95 this.store.removeItem('cat.marcedit.stack_subfields');
100 return this.context.undoCount();
103 redoCount(): number {
104 return this.context.redoCount();
108 this.context.requestUndo();
112 this.context.requestRedo();
115 controlFields(): MarcField[] {
116 return this.record.fields.filter(f => f.isCtrlField);
119 dataFields(): MarcField[] {
120 return this.record.fields.filter(f => !f.isCtrlField);
126 this.record.fields.filter(f => this.isControlledBibTag(f.tag))
130 id: f.fieldId, // ignored and echoed by server
134 subfields: f.subfields.map(sf => [sf[0], sf[1]])
138 this.net.request('open-ils.cat',
139 'open-ils.cat.authority.validate.bib_field', fields)
140 .subscribe(checkedField => {
141 const bibField = this.record.fields
142 .filter(f => f.fieldId === +checkedField.id)[0];
144 bibField.authChecked = true;
145 bibField.authValid = checkedField.valid;
149 isControlledBibTag(tag: string): boolean {
150 return this.controlledBibTags && this.controlledBibTags.includes(tag);
153 openLinkerDialog(field: MarcField) {
154 this.authLinker.bibField = field;
155 this.authLinker.open({size: 'xl'}).subscribe(newField => {
157 // The presence of newField here means the linker wants to
158 // replace the field with a new field from the authority
159 // record. Otherwise, the original field may have been
160 // directly modified or the dialog canceled.
161 if (!newField) { return; }
163 // Performs an insert followed by a delete, so the two
164 // fields can be tracked separately for undo/redo actions.
165 const marcField = this.record.newField(newField);
166 this.context.insertField(field, marcField);
167 this.context.deleteField(field);
169 // Mark the insert and delete as an atomic undo/redo action.
170 this.context.setUndoGroupSize(2);
174 // 007 Physical characteristics wizard.
175 openPhysCharDialog(field: MarcField) {
176 this.physCharDialog.fieldData = field.data;
178 this.physCharDialog.open({size: 'lg'}).subscribe(
181 this.context.requestFieldFocus({
182 fieldId: field.fieldId,