From 5368527661e9bd6e1b9d1980861532bbb8641936 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 23 Jul 2018 17:43:51 -0400 Subject: [PATCH] LP#1779158 Match set UI managing nodes Signed-off-by: Bill Erickson --- .../vandelay/match-set-expression.component.html | 117 ++++++++++++- .../cat/vandelay/match-set-expression.component.ts | 184 +++++++++++++++++++-- 2 files changed, 283 insertions(+), 18 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.html b/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.html index 4f206d58fb..98404ee584 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.html +++ b/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.html @@ -1,12 +1,124 @@ + + + NOT + Normalized Heading + {{point.bool_op()}}{{point.svf()}}{{point.tag()}} + ‡{{point.subfield()}} + | Match score {{point.quality()}} + + +
-
+
+
+ + Your Expression: {{expressionAsString()}} + +
+
+ Add New: + + + + +
+
+
+ +
+
Record Attribute:
+
+ + +
+
+
+ +
+
Tag:
+
+ +
+
+
+
Subfield ‡:
+
+ +
+
+
+ +
+
Normalized Heading:
+
+ +
+
+
+ +
+
Match Score:
+
+ +
+
+
+
Negate:
+
+ +
+
+
+ +
+
Operator:
+
+ +
+
+
+
+ +
+
+
    +
  1. Define a new match point using the above fields.
  2. +
  3. Select a boolean node in the tree.
  4. +
  5. Click the "Add..." button to add the new matchpoint + as a child of the selected node.
  6. +
+
+
+
+
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.ts index 33eb9bec62..5a77ed9882 100644 --- a/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/cat/vandelay/match-set-expression.component.ts @@ -1,8 +1,10 @@ import {Component, OnInit, ViewChild, AfterViewInit, Input} from '@angular/core'; -import {IdlObject} from '@eg/core/idl.service'; +import {IdlObject, IdlService} from '@eg/core/idl.service'; import {PcrudService} from '@eg/core/pcrud.service'; import {OrgService} from '@eg/core/org.service'; import {Tree, TreeNode} from '@eg/share/tree/tree'; +import {ComboboxEntry} from '@eg/share/combobox/combobox.component'; +import {StringService} from '@eg/share/string/string.service'; @Component({ selector: 'eg-match-set-expression', @@ -15,6 +17,7 @@ export class MatchSetExpressionComponent implements OnInit { @Input() set matchSet(ms: IdlObject) { this.matchSet_ = ms; if (ms && !this.initDone) { + this.matchSetType = ms.mtype(); this.initDone = true; this.refreshTree(); } @@ -22,35 +25,72 @@ export class MatchSetExpressionComponent implements OnInit { tree: Tree; initDone: boolean; + matchSetPoints: {[id: string]: IdlObject}; + matchSetType: string; + changesMade: boolean; + + // Current type of new match point + newPointType: string; + newRecordAttr: string; + newMatchScore: number; + newNegate: boolean; + newMarcTag: string; + newMarcSf: string; + newHeading: string; + newBoolOp: string; + newId: number; + + bibAttrDefs: IdlObject[]; + bibAttrDefEntries: ComboboxEntry[]; constructor( + private idl: IdlService, private pcrud: PcrudService, - private org: OrgService - ) { } + private org: OrgService, + private strings: StringService + ) { + this.bibAttrDefs = []; + this.bibAttrDefEntries = []; + this.matchSetPoints = {}; + this.newId = -1; + } ngOnInit() { + + this.pcrud.retrieveAll('crad', {order_by: {crad: 'label'}}) + .subscribe(attr => { + this.bibAttrDefs.push(attr); + this.bibAttrDefEntries.push({id: attr.name(), label: attr.label()}); + }); } - refreshTree() { + refreshTree(): Promise { if (!this.matchSet_) { return; } + this.matchSetPoints = {}; - this.pcrud.search('vmsp', - {match_set: this.matchSet_.id()}, {}, {atomic: true} + return this.pcrud.search('vmsp', + {match_set: this.matchSet_.id()}, {}, + {atomic: true, authoritative: true} ).toPromise().then(points => { // create tree nodes const nodes = []; const idmap: any = {}; points.forEach(point => { + + // Track the from-database point objects + this.matchSetPoints[point.id()] = point; + + point.negate(point.negate() === 't' ? true : false); + point.heading(point.heading() === 't' ? true : false); + const node = new TreeNode({ id: point.id(), expanded: true, - label: point.bool_op() - || point.svf() - || (point.tag() + ' ‡' + point.subfield()) + callerData: {point: point} }); - nodes.push(node); idmap[node.id + ''] = node; + this.setNodeLabel(node, point).then(() => nodes.push(node)); }); // then apply the parent/child relationship @@ -66,17 +106,129 @@ export class MatchSetExpressionComponent implements OnInit { }); } - nodeClicked(node: TreeNode) { - console.log('node clicked: ' + node.label - + ' expanded ' + node.expanded); + setNodeLabel(node: TreeNode, point: IdlObject): Promise { + if (node.label) { return Promise.resolve(null); } + return Promise.all([ + this.getPointLabel(point, true).then(txt => node.label = txt), + this.getPointLabel(point, false).then( + txt => node.callerData.slimLabel = txt) + ]); + } + + getPointLabel(point: IdlObject, showmatch?: boolean): Promise { + return this.strings.interpolate( + 'staff.cat.vandelay.matchpoint.label', + {point: point, showmatch: showmatch} + ); } + nodeClicked(node: TreeNode) {} + deleteNode() { - this.tree.removeNode(this.tree.activeNode()); + this.changesMade = true; + const node = this.tree.selectedNode() + const point = this.matchSetPoints[node.id]; + if (point) { + // point won't be cached if it's new during this session. + point.isdeleted(true); + } + this.tree.removeNode(node); + } + + hasSelectedNode(): boolean { + return Boolean(this.tree.selectedNode()); + } + + selectedIsBool(): boolean { + if (this.tree) { + const node = this.tree.selectedNode(); + return node && node.callerData.point.bool_op(); + } + return false; + } + + addChildNode() { + this.changesMade = true; + + const pnode = this.tree.selectedNode(); + const point = this.idl.create('vmsp'); + point.id(this.newId--); + point.isnew(true); + point.parent(pnode.id); + point.match_set(this.matchSet_.id()); + + if (this.newPointType === 'bool') { + point.bool_op(this.newBoolOp); + + } else { + + if (this.newPointType == 'attr') { + point.svf(this.newRecordAttr); + + } else if (this.newPointType == 'marc') { + point.tag(this.newMarcTag); + point.subfield(this.newMarcSf ? this.newMarcSf : null); + } else if (this.newPointType == 'heading') { + point.heading(true); + } + + point.negate(this.newNegate); + point.quality(this.newMatchScore); + } + + const node: TreeNode = new TreeNode({ + id: point.id(), + callerData: {point: point} + }); + + // Match points are added to the DB only when the tree is saved. + this.setNodeLabel(node, point).then(() => pnode.children.push(node)); + } + + setNewPointType(type_: string) { + this.newPointType = type_; + this.newRecordAttr = ''; + this.newMatchScore = 1; + this.newNegate = false; + this.newMarcTag = ''; + this.newMarcSf = ''; + this.newBoolOp = 'AND'; + } + + expressionAsString(): string { + if (!this.tree) { return ''; } + + const renderNode = (node: TreeNode): string => { + if (!node) { return ''; } + + if (node.children.length) { + return '(' + node.children.map(renderNode).join( + ' ' + node.callerData.slimLabel + ' ') + ')' + } else if (!node.callerData.point.bool_op()) { + return node.callerData.slimLabel; + } else { + return '()'; + } + } + + return renderNode(this.tree.rootNode); } - hasActiveNode(): boolean { - return Boolean(this.tree.activeNode()); + saveTree(): Promise { + + // New nodes + let nodes = this.tree.nodeList() + .filter(node => node.callerData.point.isnew()) + .map(node => node.callerData.point); + + // Deleted nodes + nodes = nodes.concat( + Object.values(this.matchSetPoints) + .filter(point => point.isdeleted()) + ); + + return this.pcrud.autoApply(nodes) + .toPromise().then(() => this.refreshTree()) } } -- 2.11.0