LP1852782 MARC editor Physical Characteristics Wizard
[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 import {PhysCharDialogComponent} from './phys-char-dialog.component';
13
14
15 /**
16  * MARC Record rich editor interface.
17  */
18
19 @Component({
20   selector: 'eg-marc-rich-editor',
21   templateUrl: './rich-editor.component.html',
22   styleUrls: ['rich-editor.component.css']
23 })
24
25 export class MarcRichEditorComponent implements OnInit {
26
27     @Input() context: MarcEditContext;
28     get record(): MarcRecord { return this.context.record; }
29
30     dataLoaded: boolean;
31     showHelp: boolean;
32     randId = Math.floor(Math.random() * 100000);
33     stackSubfields: boolean;
34     controlledBibTags: string[] = [];
35
36     @ViewChild('authLinker', {static: false})
37         authLinker: AuthorityLinkingDialogComponent;
38
39     @ViewChild('physCharDialog', {static: false})
40         physCharDialog: PhysCharDialogComponent;
41
42     constructor(
43         private idl: IdlService,
44         private net: NetService,
45         private org: OrgService,
46         private store: ServerStoreService,
47         private tagTable: TagTableService
48     ) {}
49
50     ngOnInit() {
51
52         this.store.getItem('cat.marcedit.stack_subfields')
53         .then(stack => this.stackSubfields = stack);
54
55         this.init().then(_ =>
56             this.context.recordChange.subscribe(__ => this.init()));
57
58         // Changing the Type fixed field means loading new meta-metadata.
59         this.record.fixedFieldChange.pipe(filter(code => code === 'Type'))
60         .subscribe(_ => this.init());
61     }
62
63     init(): Promise<any> {
64         this.dataLoaded = false;
65
66         if (!this.record) { return Promise.resolve(); }
67
68         return Promise.all([
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),
75             this.fetchSettings()
76         ]).then(_ =>
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)
83         );
84     }
85
86     fetchSettings(): Promise<any> {
87         // Fetch at rich editor load time to cache.
88         return this.org.settings(['cat.marc_control_number_identifier']);
89     }
90
91     stackSubfieldsChange() {
92         if (this.stackSubfields) {
93             this.store.setItem('cat.marcedit.stack_subfields', true);
94         } else {
95             this.store.removeItem('cat.marcedit.stack_subfields');
96         }
97     }
98
99     undoCount(): number {
100         return this.context.undoCount();
101     }
102
103     redoCount(): number {
104         return this.context.redoCount();
105     }
106
107     undo() {
108         this.context.requestUndo();
109     }
110
111     redo() {
112         this.context.requestRedo();
113     }
114
115     controlFields(): MarcField[] {
116         return this.record.fields.filter(f => f.isCtrlField);
117     }
118
119     dataFields(): MarcField[] {
120         return this.record.fields.filter(f => !f.isCtrlField);
121     }
122
123     validate() {
124         const fields = [];
125
126         this.record.fields.filter(f => this.isControlledBibTag(f.tag))
127         .forEach(f => {
128             f.authValid = false;
129             fields.push({
130                 id: f.fieldId, // ignored and echoed by server
131                 tag: f.tag,
132                 ind1: f.ind1,
133                 ind2: f.ind2,
134                 subfields: f.subfields.map(sf => [sf[0], sf[1]])
135             });
136         });
137
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];
143
144             bibField.authChecked = true;
145             bibField.authValid = checkedField.valid;
146         });
147     }
148
149     isControlledBibTag(tag: string): boolean {
150         return this.controlledBibTags && this.controlledBibTags.includes(tag);
151     }
152
153     openLinkerDialog(field: MarcField) {
154         this.authLinker.bibField = field;
155         this.authLinker.open({size: 'xl'}).subscribe(newField => {
156
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; }
162
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);
168
169             // Mark the insert and delete as an atomic undo/redo action.
170             this.context.setUndoGroupSize(2);
171         });
172     }
173
174     // 007 Physical characteristics wizard.
175     openPhysCharDialog(field: MarcField) {
176         this.physCharDialog.fieldData = field.data;
177
178         this.physCharDialog.open({size: 'lg'}).subscribe(
179             newData => {
180                 if (newData) {
181                     this.context.requestFieldFocus({
182                         fieldId: field.fieldId,
183                         target: 'cfld',
184                         newText: newData
185                     });
186                 }
187             }
188         );
189     }
190 }
191
192
193