|
26 | 26 | // build this into a Map[packageId -> packageWidget.selectedProp] |
27 | 27 | var stateTemplate = {} |
28 | 28 |
|
| 29 | + // build this into a Map[packageId -> packageWidget.instrumentationSelectedProp] |
| 30 | + var instrumentedTemplate = {} |
| 31 | + |
29 | 32 | var widgets = {} |
30 | 33 |
|
| 34 | + function eligibleForWidget(node){ |
| 35 | + if(!node) return false |
| 36 | + |
| 37 | + return node.kind == 'package' || |
| 38 | + node.kind == 'group' || |
| 39 | + node.kind == 'root' |
| 40 | + } |
| 41 | + |
| 42 | + // Get the partial selection state for the trace instrumentation flag, for nodes that |
| 43 | + // would exist in the package list. Note that <self> nodes will take their original |
| 44 | + // node's `id` and `traced` properties, and the original's `traced` will be set to |
| 45 | + // undefined. This function sets a `partialTraceSelection` value on nodes recursively, |
| 46 | + // with `1 = fully selected`, `0 = unselected`, and `undefined = partially selected`. |
| 47 | + (function calculatePartialTraceSelections(node){ |
| 48 | + var isFullSelected = true, |
| 49 | + isPartialSelected = false |
| 50 | + |
| 51 | + if(node.traced == 0) isFullSelected = false |
| 52 | + |
| 53 | + node.children.forEach(function(kid){ |
| 54 | + if(eligibleForWidget(kid)){ |
| 55 | + var kidSelection = calculatePartialTraceSelections(kid) |
| 56 | + if(!kidSelection) isFullSelected = false |
| 57 | + if(kidSelection || kidSelection == undefined) isPartialSelected = true |
| 58 | + } |
| 59 | + }) |
| 60 | + |
| 61 | + var selectionValue = isFullSelected ? 1 : isPartialSelected ? undefined : 0 |
| 62 | + node.partialTraceSelection = selectionValue |
| 63 | + return selectionValue |
| 64 | + })(treeData.root) |
| 65 | + |
| 66 | + // special case: the root is never selected |
| 67 | + treeData.root.partialTraceSelection = 0 |
| 68 | + |
31 | 69 | // initialize the `stateTemplate` and `widgets` maps |
32 | 70 | // based on the package nodes in `treeData` |
33 | 71 | ;(function setupTreeHierarchy(packageParentNode, node){ |
34 | | - if(node.kind == 'package' || node.kind == 'root'){ |
| 72 | + if(eligibleForWidget(node)){ |
35 | 73 |
|
36 | 74 | var pw = new PackageWidget() |
37 | 75 | widgets[node.id] = pw |
38 | 76 |
|
| 77 | + pw.instrumentationSelected(node.partialTraceSelection) |
| 78 | + |
39 | 79 | pw.collapseChildren(/* collapsed = */true, /* animate = */false) |
40 | 80 |
|
41 | 81 | stateTemplate[node.id] = pw.selectedProp |
42 | 82 | pw.selectionClicks.onValue(function(){ |
43 | 83 | handleSelectionClick(node, pw) |
44 | 84 | }) |
45 | 85 |
|
| 86 | + instrumentedTemplate[node.id] = pw.instrumentationSelectedProp |
| 87 | + pw.instrumentationSelectedClicks.onValue(function(){ |
| 88 | + handleInstrumentationSelectionClick(node, pw) |
| 89 | + }) |
| 90 | + |
46 | 91 | pw.uiParts.collapser.click(function(event){ |
47 | 92 | pw.collapseChildren('toggle', true) |
48 | 93 | event.stopPropagation() |
|
52 | 97 | pw.uiParts.main.appendTo($totalsContainer) |
53 | 98 | pw.abbreviatedLabel('Overall Coverage') |
54 | 99 | pw.selectable(false) |
| 100 | + pw.instrumentationSelectable(false) |
55 | 101 | } |
56 | 102 |
|
57 | | - if(node.kind == 'package'){ |
| 103 | + if(node.kind == 'group' || node.kind == 'package'){ |
58 | 104 | if(packageParentNode){ |
59 | 105 | widgets[packageParentNode.id].children.add(pw) |
60 | 106 | } else { |
|
64 | 110 | if(node.isSelfNode){ |
65 | 111 | pw.fullLabel(node.name).abbreviatedLabel(node.name) |
66 | 112 | } else { |
67 | | - var parentName = packageParentNode ? packageParentNode.name : '', |
68 | | - abbrevName = node.name.substr(parentName.length) |
| 113 | + var abbrevName |
| 114 | + |
| 115 | + if (node.kind != 'group' && packageParentNode && packageParentNode.kind == 'group') |
| 116 | + abbrevName = node.name |
| 117 | + else { |
| 118 | + var parentName = packageParentNode ? packageParentNode.name : '', |
| 119 | + abbrevName = node.name.substr(parentName.length) |
| 120 | + } |
69 | 121 |
|
70 | 122 | pw.fullLabel(node.name).abbreviatedLabel(abbrevName) |
71 | 123 | } |
72 | 124 | } |
73 | 125 | } |
74 | 126 |
|
75 | 127 | ;(node.children || []).forEach(function(kid){ |
76 | | - var nextParent = (node.kind == 'package')? node : packageParentNode |
| 128 | + var nextParent = (node.kind == 'group' || node.kind == 'package')? node : packageParentNode |
77 | 129 | setupTreeHierarchy(nextParent, kid) |
78 | 130 | }) |
79 | 131 |
|
|
87 | 139 | return found |
88 | 140 | } |
89 | 141 |
|
90 | | - function handleSelectionClick(node, widget){ |
91 | | - // selected may be one of [1, 0, undefined]. |
92 | | - // transition [1 -> 0], [0 -> 1], [undefined -> 1] |
93 | | - var newSel = +!widget.selected() |
94 | | - |
95 | | - // apply the new selection to this node and each of its children |
96 | | - ;(function bubbleDown(w){ |
97 | | - w.selected(newSel) |
98 | | - w.children.forEach(bubbleDown) |
99 | | - })(widget) |
100 | | - |
101 | | - // climb the tree, re-checking the full/partial selection state of node's ancestors |
102 | | - ;(function bubbleUp(n){ |
103 | | - if(!n || n.kind == 'root') return |
104 | | - var w = widgets[n.id] |
105 | | - if(!w) return |
106 | | - |
107 | | - var isFullSelected = true, |
108 | | - isPartialSelected = false |
109 | | - |
110 | | - w.children.forEach(function(c){ |
111 | | - var s = c.selected() |
112 | | - if(!s) isFullSelected = false |
113 | | - if(s == 1 || s == undefined) isPartialSelected = true |
114 | | - }) |
| 142 | + // Disable all of the widgets while the trace is running |
| 143 | + Trace.running.onValue(function(isRunning){ |
| 144 | + for(var id in widgets){ |
| 145 | + var pw = widgets[id], |
| 146 | + node = treeData.getNode(id) |
| 147 | + if(node.kind != 'root'){ |
| 148 | + pw.instrumentationSelectable(!isRunning) |
| 149 | + } |
| 150 | + } |
| 151 | + }) |
115 | 152 |
|
116 | | - var s = isFullSelected ? 1 : isPartialSelected ? undefined : 0 |
117 | | - w.selected(s) |
| 153 | + // checkSelected = function(widget){ return <is widget selected> } |
| 154 | + // setSelected = function(widget, sel){ <set widget.selected to sel> } |
| 155 | + // Returns a function(node, widget): |
| 156 | + // node is the starting point in the data tree |
| 157 | + // widget is the corresponding widget for the node |
| 158 | + // |
| 159 | + // The function applies partial selection logic as if the widget |
| 160 | + // had been clicked. |
| 161 | + function bubblePartialSelection(checkSelected, setSelected){ |
| 162 | + return function(node, widget){ |
| 163 | + // selected may be one of [1, 0, undefined]. |
| 164 | + // transition [1 -> 0], [0 -> 1], [undefined -> 1] |
| 165 | + var newSel = +!checkSelected(widget) |
| 166 | + |
| 167 | + // apply the new selection to this node and each of its children |
| 168 | + ;(function bubbleDown(w){ |
| 169 | + setSelected(w, newSel) |
| 170 | + w.children.forEach(bubbleDown) |
| 171 | + })(widget) |
| 172 | + |
| 173 | + // climb the tree, re-checking the full/partial selection state of node's ancestors |
| 174 | + ;(function bubbleUp(n){ |
| 175 | + if(!n || n.kind == 'root') return |
| 176 | + var w = widgets[n.id] |
| 177 | + if(!w) return |
| 178 | + |
| 179 | + var isFullSelected = true, |
| 180 | + isPartialSelected = false |
| 181 | + |
| 182 | + w.children.forEach(function(c){ |
| 183 | + var s = checkSelected(c) |
| 184 | + if(!s) isFullSelected = false |
| 185 | + if(s == 1 || s == undefined) isPartialSelected = true |
| 186 | + }) |
| 187 | + |
| 188 | + var s = isFullSelected ? 1 : isPartialSelected ? undefined : 0 |
| 189 | + setSelected(w, s) |
| 190 | + |
| 191 | + bubbleUp(n.parent) |
| 192 | + |
| 193 | + })(node.parent) |
| 194 | + var p = node.parent, pw = p && widgets[p.id] |
| 195 | + } |
| 196 | + } |
118 | 197 |
|
119 | | - bubbleUp(n.parent) |
| 198 | + // a function(node,widget) that toggles the tri-state 'selected' property, |
| 199 | + // starting from the given node+widget |
| 200 | + var handleSelectionClick = bubblePartialSelection( |
| 201 | + /*get*/ function(w){ return w.selected() }, |
| 202 | + /*set*/ function(w,s){ return w.selected(s) } |
| 203 | + ) |
120 | 204 |
|
121 | | - })(node.parent) |
122 | | - var p = node.parent, pw = p && widgets[p.id] |
123 | | - } |
| 205 | + // a function(node, widget) that toggles the tri-state 'instrumentationSelected' |
| 206 | + // property, starting from the given node+widget |
| 207 | + var handleInstrumentationSelectionClick = bubblePartialSelection( |
| 208 | + /*get*/ function(w){ return w.instrumentationSelected() }, |
| 209 | + /*set*/ function(w,s){ return w.instrumentationSelected(s) } |
| 210 | + ) |
124 | 211 |
|
125 | 212 | // set the `methodCount` property for all widgets |
126 | 213 | applyMethodCounts(treeData, widgets) |
|
131 | 218 | */ |
132 | 219 | this.selectedWidgets = Bacon.combineTemplate(stateTemplate).debounce(10) |
133 | 220 |
|
| 221 | + /** |
| 222 | + * Exposes the 'instrumented' state of each of the widgets, as |
| 223 | + * a Map[package.id -> isInstrumented] |
| 224 | + */ |
| 225 | + this.instrumentedWidgets = Bacon.combineTemplate(instrumentedTemplate).debounce(10) |
| 226 | + |
134 | 227 | // Decide whether or not to show the "clear all selections" button |
135 | 228 | // depending on whether or not there are selected widgets |
136 | 229 | this.selectedWidgets |
|
0 commit comments