]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts
LP1852782 MARC editor authority linking support
[working/Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / share / marc-edit / rich-editor.component.ts
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
13
14 /**
15  * MARC Record rich editor interface.
16  */
17
18 @Component({
19   selector: 'eg-marc-rich-editor',
20   templateUrl: './rich-editor.component.html',
21   styleUrls: ['rich-editor.component.css']
22 })
23
24 export class MarcRichEditorComponent implements OnInit {
25
26     @Input() context: MarcEditContext;
27     get record(): MarcRecord { return this.context.record; }
28
29     dataLoaded: boolean;
30     showHelp: boolean;
31     randId = Math.floor(Math.random() * 100000);
32     stackSubfields: boolean;
33     controlledBibTags: string[] = [];
34
35     @ViewChild('authLinker', {static: false})
36         authLinker: AuthorityLinkingDialogComponent;
37
38     constructor(
39         private idl: IdlService,
40         private net: NetService,
41         private org: OrgService,
42         private store: ServerStoreService,
43         private tagTable: TagTableService
44     ) {}
45
46     ngOnInit() {
47
48         this.store.getItem('cat.marcedit.stack_subfields')
49         .then(stack => this.stackSubfields = stack);
50
51         this.init().then(_ =>
52             this.context.recordChange.subscribe(__ => this.init()));
53
54         // Changing the Type fixed field means loading new meta-metadata.
55         this.record.fixedFieldChange.pipe(filter(code => code === 'Type'))
56         .subscribe(_ => this.init());
57     }
58
59     init(): Promise<any> {
60         this.dataLoaded = false;
61
62         if (!this.record) { return Promise.resolve(); }
63
64         return Promise.all([
65             this.tagTable.loadTagTable({marcRecordType: this.context.recordType}),
66             this.tagTable.getFfPosTable(this.record.recordType()),
67             this.tagTable.getFfValueTable(this.record.recordType()),
68             this.tagTable.getControlledBibTags().then(
69                 tags => this.controlledBibTags = tags)
70         ]).then(_ =>
71             // setTimeout forces all of our sub-components to rerender
72             // themselves each time init() is called.  Without this,
73             // changing the record Type would only re-render the fixed
74             // fields editor when data had to be fetched from the
75             // network.  (Sometimes the data is cached).
76             setTimeout(() => this.dataLoaded = true)
77         );
78     }
79
80     stackSubfieldsChange() {
81         if (this.stackSubfields) {
82             this.store.setItem('cat.marcedit.stack_subfields', true);
83         } else {
84             this.store.removeItem('cat.marcedit.stack_subfields');
85         }
86     }
87
88     undoCount(): number {
89         return this.context.undoCount();
90     }
91
92     redoCount(): number {
93         return this.context.redoCount();
94     }
95
96     undo() {
97         this.context.requestUndo();
98     }
99
100     redo() {
101         this.context.requestRedo();
102     }
103
104     controlFields(): MarcField[] {
105         return this.record.fields.filter(f => f.isCtrlField);
106     }
107
108     dataFields(): MarcField[] {
109         return this.record.fields.filter(f => !f.isCtrlField);
110     }
111
112     validate() {
113         const fields = [];
114         this.record.fields.filter(f => this.isControlledBibTag(f.tag))
115
116         .forEach(f => {
117             f.authValid = false;
118             fields.push({
119                 id: f.fieldId, // ignored and echoed by server
120                 tag: f.tag,
121                 ind1: f.ind1,
122                 ind2: f.ind2,
123                 subfields: f.subfields.map(sf => ({code: sf[0], value: sf[1]}))
124             });
125         });
126
127         this.net.request('open-ils.cat',
128             'open-ils.cat.authority.validate.bib_field', fields)
129         .subscribe(checkedField => {
130             const bibField = this.record.fields
131                 .filter(f => f.fieldId === +checkedField.id)[0];
132
133             bibField.authChecked = true;
134             bibField.authValid = checkedField.valid;
135         });
136     }
137
138     isControlledBibTag(tag: string): boolean {
139         return this.controlledBibTags && this.controlledBibTags.includes(tag);
140     }
141
142     openLinkerDialog(field: MarcField) {
143         this.authLinker.bibField = field;
144         this.authLinker.open({size: 'lg'}).subscribe(newField => {
145             if (!newField) { return; }
146
147             // Performs an insert followed by a delete, so the two
148             // fields can be tracked separately for undo/redo actions.
149             const marcField = this.record.newField(newField);
150             this.context.insertField(field, marcField);
151             this.context.deleteField(field);
152
153             // Mark the insert and delete as an atomic undo/redo action.
154             this.context.setUndoGroupSize(2);
155         });
156     }
157 }
158
159
160