]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/eg2/src/app/staff/share/marc-edit/rich-editor.component.ts
e4b3da641d82f694aca1b31b9160bdf924c612c1
[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.loadTags({
66                 marcRecordType: this.context.recordType,
67                 ffType: this.record.recordType()
68             }).then(table => this.context.tagTable = table),
69             this.tagTable.getControlledBibTags().then(
70                 tags => this.controlledBibTags = tags),
71             this.fetchSettings()
72         ]).then(_ =>
73             // setTimeout forces all of our sub-components to rerender
74             // themselves each time init() is called.  Without this,
75             // changing the record Type would only re-render the fixed
76             // fields editor when data had to be fetched from the
77             // network.  (Sometimes the data is cached).
78             setTimeout(() => this.dataLoaded = true)
79         );
80     }
81
82     fetchSettings(): Promise<any> {
83         // Fetch at rich editor load time to cache.
84         return this.org.settings(['cat.marc_control_number_identifier']);
85     }
86
87     stackSubfieldsChange() {
88         if (this.stackSubfields) {
89             this.store.setItem('cat.marcedit.stack_subfields', true);
90         } else {
91             this.store.removeItem('cat.marcedit.stack_subfields');
92         }
93     }
94
95     undoCount(): number {
96         return this.context.undoCount();
97     }
98
99     redoCount(): number {
100         return this.context.redoCount();
101     }
102
103     undo() {
104         this.context.requestUndo();
105     }
106
107     redo() {
108         this.context.requestRedo();
109     }
110
111     controlFields(): MarcField[] {
112         return this.record.fields.filter(f => f.isCtrlField);
113     }
114
115     dataFields(): MarcField[] {
116         return this.record.fields.filter(f => !f.isCtrlField);
117     }
118
119     validate() {
120         const fields = [];
121
122         this.record.fields.filter(f => this.isControlledBibTag(f.tag))
123         .forEach(f => {
124             f.authValid = false;
125             fields.push({
126                 id: f.fieldId, // ignored and echoed by server
127                 tag: f.tag,
128                 ind1: f.ind1,
129                 ind2: f.ind2,
130                 subfields: f.subfields.map(sf => [sf[0], sf[1]])
131             });
132         });
133
134         this.net.request('open-ils.cat',
135             'open-ils.cat.authority.validate.bib_field', fields)
136         .subscribe(checkedField => {
137             const bibField = this.record.fields
138                 .filter(f => f.fieldId === +checkedField.id)[0];
139
140             bibField.authChecked = true;
141             bibField.authValid = checkedField.valid;
142         });
143     }
144
145     isControlledBibTag(tag: string): boolean {
146         return this.controlledBibTags && this.controlledBibTags.includes(tag);
147     }
148
149     openLinkerDialog(field: MarcField) {
150         this.authLinker.bibField = field;
151         this.authLinker.open({size: 'xl'}).subscribe(newField => {
152             if (!newField) { return; }
153
154             // Performs an insert followed by a delete, so the two
155             // fields can be tracked separately for undo/redo actions.
156             const marcField = this.record.newField(newField);
157             this.context.insertField(field, marcField);
158             this.context.deleteField(field);
159
160             // Mark the insert and delete as an atomic undo/redo action.
161             this.context.setUndoGroupSize(2);
162         });
163     }
164 }
165
166
167