1 import {Component, OnInit, ViewChild, AfterViewInit, Input} from '@angular/core';
2 import {IdlObject, IdlService} from '@eg/core/idl.service';
3 import {PcrudService} from '@eg/core/pcrud.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 {Tree, TreeNode} from '@eg/share/tree/tree';
8 import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
9 import {StringService} from '@eg/share/string/string.service';
10 import {MatchSetNewPointComponent} from './match-set-new-point.component';
13 selector: 'eg-match-set-expression',
14 templateUrl: 'match-set-expression.component.html'
16 export class MatchSetExpressionComponent implements OnInit {
18 // Match set arrives from parent async.
20 @Input() set matchSet(ms: IdlObject) {
22 if (ms && !this.initDone) {
23 this.matchSetType = ms.mtype();
34 // Current type of new match point
38 @ViewChild('newPoint', { static: true }) newPoint: MatchSetNewPointComponent;
41 private idl: IdlService,
42 private pcrud: PcrudService,
43 private net: NetService,
44 private auth: AuthService,
45 private org: OrgService,
46 private strings: StringService
53 refreshTree(): Promise<any> {
54 if (!this.matchSet_) { return Promise.resolve(); }
56 return this.pcrud.search('vmsp',
57 {match_set: this.matchSet_.id()}, {},
58 {atomic: true, authoritative: true}
59 ).toPromise().then(points => {
60 if (points.length > 0) {
61 this.ingestMatchPoints(points);
68 // When creating a new tree, add a stub boolean node
69 // as the root so the tree has something to render.
72 const point = this.idl.create('vmsp');
73 point.id(this.newId--);
75 point.match_set(this.matchSet_.id());
79 const node: TreeNode = new TreeNode({
81 callerData: {point: point}
84 this.tree = new Tree(node);
85 this.setNodeLabel(node, point);
88 // Tree-ify a set of match points.
89 ingestMatchPoints(points: IdlObject[]) {
91 const idmap: any = {};
93 // massage data, create tree nodes
94 points.forEach(point => {
96 point.negate(point.negate() === 't' ? true : false);
97 point.heading(point.heading() === 't' ? true : false);
100 const node = new TreeNode({
103 callerData: {point: point}
105 idmap[node.id + ''] = node;
106 this.setNodeLabel(node, point).then(() => nodes.push(node));
109 // apply the tree parent/child relationships
110 points.forEach(point => {
111 const node = idmap[point.id() + ''];
112 if (point.parent()) {
113 idmap[point.parent() + ''].children.push(node);
115 this.tree = new Tree(node);
120 setNodeLabel(node: TreeNode, point: IdlObject): Promise<any> {
121 if (node.label) { return Promise.resolve(null); }
123 this.getPointLabel(point, true).then(txt => node.label = txt),
124 this.getPointLabel(point, false).then(
125 txt => node.callerData.slimLabel = txt)
129 getPointLabel(point: IdlObject, showmatch?: boolean): Promise<string> {
130 return this.strings.interpolate(
131 'staff.cat.vandelay.matchpoint.label',
132 {point: point, showmatch: showmatch}
136 nodeClicked(node: TreeNode) {}
139 this.changesMade = true;
140 const node = this.tree.selectedNode();
141 this.tree.removeNode(node);
144 hasSelectedNode(): boolean {
145 return Boolean(this.tree.selectedNode());
148 isRootNode(): boolean {
149 const node = this.tree.selectedNode();
150 if (node && this.tree.findParentNode(node) === null) {
156 selectedIsBool(): boolean {
158 const node = this.tree.selectedNode();
159 return node && node.callerData.point.bool_op();
165 this.changesMade = true;
167 const pnode = this.tree.selectedNode();
168 const point = this.idl.create('vmsp');
169 point.id(this.newId--);
171 point.parent(pnode.id);
172 point.match_set(this.matchSet_.id());
175 const ptype = this.newPoint.values.pointType;
177 if (ptype === 'bool') {
178 point.bool_op(this.newPoint.values.boolOp);
182 if (ptype === 'attr') {
183 point.svf(this.newPoint.values.recordAttr);
185 } else if (ptype === 'marc') {
186 point.tag(this.newPoint.values.marcTag);
187 point.subfield(this.newPoint.values.marcSf);
188 } else if (ptype === 'heading') {
192 point.negate(this.newPoint.values.negate);
193 point.quality(this.newPoint.values.matchScore);
196 const node: TreeNode = new TreeNode({
198 callerData: {point: point}
201 // Match points are added to the DB only when the tree is saved.
202 this.setNodeLabel(node, point).then(() => pnode.children.push(node));
205 expressionAsString(): string {
206 if (!this.tree) { return ''; }
208 const renderNode = (node: TreeNode): string => {
209 if (!node) { return ''; }
211 if (node.children.length) {
212 return '(' + node.children.map(renderNode).join(
213 ' ' + node.callerData.slimLabel + ' ') + ')';
214 } else if (!node.callerData.point.bool_op()) {
215 return node.callerData.slimLabel;
221 return renderNode(this.tree.rootNode);
224 // Server API deletes and recreates the tree on update.
225 // It manages parent/child relationships via the children array.
226 // We only need send the current tree in a form the API recognizes.
227 saveTree(): Promise<any> {
230 const compileTree = (node?: TreeNode) => {
232 if (!node) { node = this.tree.rootNode; }
234 const point = node.callerData.point;
236 node.children.forEach(child =>
237 point.children().push(compileTree(child)));
242 const rootPoint: IdlObject = compileTree();
244 return this.net.request(
246 'open-ils.vandelay.match_set.update',
247 this.auth.token(), this.matchSet_.id(), rootPoint
249 ok => this.refreshTree(),
250 err => console.error(err)