1 import {Component, ViewChild, 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 {StringService} from '@eg/share/string/string.service';
9 import {MatchSetNewPointComponent} from './match-set-new-point.component';
12 selector: 'eg-match-set-expression',
13 templateUrl: 'match-set-expression.component.html'
15 export class MatchSetExpressionComponent {
17 // Match set arrives from parent async.
19 @Input() set matchSet(ms: IdlObject) {
21 if (ms && !this.initDone) {
22 this.matchSetType = ms.mtype();
33 // Current type of new match point
37 @ViewChild('newPoint', { static: true }) newPoint: MatchSetNewPointComponent;
40 private idl: IdlService,
41 private pcrud: PcrudService,
42 private net: NetService,
43 private auth: AuthService,
44 private org: OrgService,
45 private strings: StringService
50 refreshTree(): Promise<any> {
51 if (!this.matchSet_) { return Promise.resolve(); }
53 return this.pcrud.search('vmsp',
54 {match_set: this.matchSet_.id()}, {},
55 {atomic: true, authoritative: true}
56 ).toPromise().then(points => {
57 if (points.length > 0) {
58 this.ingestMatchPoints(points);
65 // When creating a new tree, add a stub boolean node
66 // as the root so the tree has something to render.
69 const point = this.idl.create('vmsp');
70 point.id(this.newId--);
72 point.match_set(this.matchSet_.id());
76 const node: TreeNode = new TreeNode({
78 callerData: {point: point}
81 this.tree = new Tree(node);
82 this.setNodeLabel(node, point);
85 // Tree-ify a set of match points.
86 ingestMatchPoints(points: IdlObject[]) {
88 const idmap: any = {};
90 // massage data, create tree nodes
91 points.forEach(point => {
93 point.negate(point.negate() === 't' ? true : false);
94 point.heading(point.heading() === 't' ? true : false);
97 const node = new TreeNode({
100 callerData: {point: point}
102 idmap[node.id + ''] = node;
103 this.setNodeLabel(node, point).then(() => nodes.push(node));
106 // apply the tree parent/child relationships
107 points.forEach(point => {
108 const node = idmap[point.id() + ''];
109 if (point.parent()) {
110 idmap[point.parent() + ''].children.push(node);
112 this.tree = new Tree(node);
117 setNodeLabel(node: TreeNode, point: IdlObject): Promise<any> {
118 if (node.label) { return Promise.resolve(null); }
120 this.getPointLabel(point, true).then(txt => node.label = txt),
121 this.getPointLabel(point, false).then(
122 txt => node.callerData.slimLabel = txt)
126 getPointLabel(point: IdlObject, showmatch?: boolean): Promise<string> {
127 return this.strings.interpolate(
128 'staff.cat.vandelay.matchpoint.label',
129 {point: point, showmatch: showmatch}
133 nodeClicked(node: TreeNode) {}
136 this.changesMade = true;
137 const node = this.tree.selectedNode();
138 this.tree.removeNode(node);
141 hasSelectedNode(): boolean {
142 return Boolean(this.tree.selectedNode());
145 isRootNode(): boolean {
146 const node = this.tree.selectedNode();
147 if (node && this.tree.findParentNode(node) === null) {
153 selectedIsBool(): boolean {
155 const node = this.tree.selectedNode();
156 return node && node.callerData.point.bool_op();
161 addChildNode(replace?: boolean) {
162 this.changesMade = true;
164 const targetNode: TreeNode = this.tree.selectedNode();
168 point = targetNode.callerData.point;
169 point.ischanged(true);
170 // Clear previous data
171 ['bool_op', 'svf', 'tag', 'subfield', 'heading']
172 .forEach(f => point[f](null));
175 point = this.idl.create('vmsp');
176 point.id(this.newId--);
178 point.parent(targetNode.id);
179 point.match_set(this.matchSet_.id());
183 const ptype = this.newPoint.values.pointType;
185 if (ptype === 'bool') {
186 point.bool_op(this.newPoint.values.boolOp);
190 if (ptype === 'attr') {
191 point.svf(this.newPoint.values.recordAttr);
193 } else if (ptype === 'marc') {
194 point.tag(this.newPoint.values.marcTag);
195 point.subfield(this.newPoint.values.marcSf);
196 } else if (ptype === 'heading') {
200 point.negate(this.newPoint.values.negate);
201 point.quality(this.newPoint.values.matchScore);
206 targetNode.label = null;
207 this.setNodeLabel(targetNode, point);
211 const node: TreeNode = new TreeNode({
213 callerData: {point: point}
216 // Match points are added to the DB only when the tree is saved.
217 this.setNodeLabel(node, point)
218 .then(() => targetNode.children.push(node));
222 expressionAsString(): string {
223 if (!this.tree) { return ''; }
225 const renderNode = (node: TreeNode): string => {
226 if (!node) { return ''; }
228 if (node.children.length) {
229 return '(' + node.children.map(renderNode).join(
230 ' ' + node.callerData.slimLabel + ' ') + ')';
231 } else if (!node.callerData.point.bool_op()) {
232 return node.callerData.slimLabel;
238 return renderNode(this.tree.rootNode);
241 // Server API deletes and recreates the tree on update.
242 // It manages parent/child relationships via the children array.
243 // We only need send the current tree in a form the API recognizes.
244 saveTree(): Promise<any> {
247 const compileTree = (node?: TreeNode) => {
249 if (!node) { node = this.tree.rootNode; }
251 const point = node.callerData.point;
253 node.children.forEach(child =>
254 point.children().push(compileTree(child)));
259 const rootPoint: IdlObject = compileTree();
261 return this.net.request(
263 'open-ils.vandelay.match_set.update',
264 this.auth.token(), this.matchSet_.id(), rootPoint
266 ok => this.refreshTree(),
267 err => console.error(err)