|
32a2fff…
|
drh
|
1 |
/* This module contains javascript needed to render timeline graphs in Fossil. |
|
32a2fff…
|
drh
|
2 |
** |
|
6908832…
|
drh
|
3 |
** There can be multiple graphs on a single webpage, but this script is only |
|
6908832…
|
drh
|
4 |
** loaded once. |
|
6908832…
|
drh
|
5 |
** |
|
32a2fff…
|
drh
|
6 |
** Prior to sourcing this script, there should be a separate |
|
68635d6…
|
drh
|
7 |
** <script type='application/json' id='timeline-data-NN'> for each graph, |
|
68635d6…
|
drh
|
8 |
** each containing JSON like this: |
|
32a2fff…
|
drh
|
9 |
** |
|
68635d6…
|
drh
|
10 |
** { "iTableId": INTEGER, // Table sequence number (NN) |
|
68635d6…
|
drh
|
11 |
** "circleNodes": BOOLEAN, // True for circle nodes. False for squares |
|
32a2fff…
|
drh
|
12 |
** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit |
|
32a2fff…
|
drh
|
13 |
** "iRailPitch": INTEGER, // Spacing between vertical lines (px) |
|
32a2fff…
|
drh
|
14 |
** "nomo": BOOLEAN, // True to join merge lines with rails |
|
32a2fff…
|
drh
|
15 |
** "iTopRow": INTEGER, // Index of top-most row in the graph |
|
32a2fff…
|
drh
|
16 |
** "omitDescenders": BOOLEAN, // Omit ancestor lines off bottom of screen |
|
32a2fff…
|
drh
|
17 |
** "fileDiff": BOOLEAN, // True for file diff. False for check-in |
|
cee4bcf…
|
drh
|
18 |
** "scrollToSelect": BOOLEAN, // Scroll to selection on first render |
|
32a2fff…
|
drh
|
19 |
** "nrail": INTEGER, // Number of vertical "rails" |
|
32a2fff…
|
drh
|
20 |
** "baseUrl": TEXT, // Top-level URL |
|
6908832…
|
drh
|
21 |
** "dwellTimeout": INTEGER, // Tooltip show delay in milliseconds |
|
6908832…
|
drh
|
22 |
** "closeTimeout": INTEGER, // Tooltip close delay in milliseconds |
|
6908832…
|
drh
|
23 |
** "hashDigits": INTEGER, // Limit of tooltip hashes ("hash-digits") |
|
32a2fff…
|
drh
|
24 |
** "rowinfo": ROWINFO-ARRAY } |
|
32a2fff…
|
drh
|
25 |
** |
|
32a2fff…
|
drh
|
26 |
** The rowinfo field is an array of structures, one per entry in the timeline, |
|
32a2fff…
|
drh
|
27 |
** where each structure has the following fields: |
|
32a2fff…
|
drh
|
28 |
** |
|
32a2fff…
|
drh
|
29 |
** id: The id of the <div> element for the row. This is an integer. |
|
32a2fff…
|
drh
|
30 |
** to get an actual id, prepend "m" to the integer. The top node |
|
30aec47…
|
drh
|
31 |
** is iTopRow and numbers increase moving down the timeline. |
|
32a2fff…
|
drh
|
32 |
** bg: The background color for this row |
|
32a2fff…
|
drh
|
33 |
** r: The "rail" that the node for this row sits on. The left-most |
|
32a2fff…
|
drh
|
34 |
** rail is 0 and the number increases to the right. |
|
30aec47…
|
drh
|
35 |
** d: If exists and true then there is a "descender" - an arrow |
|
30aec47…
|
drh
|
36 |
** coming from the bottom of the page straight up to this node. |
|
30aec47…
|
drh
|
37 |
** mo: "merge-out". If it exists, this is the rail position |
|
4da95b2…
|
drh
|
38 |
** for the upward portion of a merge arrow. The merge arrow goes as |
|
4da95b2…
|
drh
|
39 |
** a solid normal merge line up to the row identified by "mu" and |
|
4da95b2…
|
drh
|
40 |
** then as a dashed cherrypick merge line up further to "cu". |
|
4da95b2…
|
drh
|
41 |
** If this value is omitted if there are no merge children. |
|
32a2fff…
|
drh
|
42 |
** mu: The id of the row which is the top of the merge-out arrow. |
|
30aec47…
|
drh
|
43 |
** Only exists if "mo" exists. |
|
4da95b2…
|
drh
|
44 |
** cu: Extend the mu merge arrow up to this row as a cherrypick |
|
4da95b2…
|
drh
|
45 |
** merge line, if this value exists. |
|
32a2fff…
|
drh
|
46 |
** u: Draw a thick child-line out of the top of this node and up to |
|
32a2fff…
|
drh
|
47 |
** the node with an id equal to this value. 0 if it is straight to |
|
32a2fff…
|
drh
|
48 |
** the top of the page, -1 if there is no thick-line riser. |
|
32a2fff…
|
drh
|
49 |
** f: 0x01: a leaf node. |
|
32a2fff…
|
drh
|
50 |
** au: An array of integers that define thick-line risers for branches. |
|
32a2fff…
|
drh
|
51 |
** The integers are in pairs. For each pair, the first integer is |
|
32a2fff…
|
drh
|
52 |
** is the rail on which the riser should run and the second integer |
|
30aec47…
|
drh
|
53 |
** is the id of the node upto which the riser should run. If there |
|
30aec47…
|
drh
|
54 |
** are no risers, this array does not exist. |
|
68635d6…
|
drh
|
55 |
** mi: "merge-in". An array of integer rail positions from which |
|
68635d6…
|
drh
|
56 |
** merge arrows should be drawn into this node. If the value is |
|
2bbd70d…
|
drh
|
57 |
** negative, then the rail position is -1-mi[] and a thin merge-arrow |
|
2bbd70d…
|
drh
|
58 |
** descender is drawn to the bottom of the screen. This array is |
|
2bbd70d…
|
drh
|
59 |
** omitted if there are no inbound merges. |
|
30aec47…
|
drh
|
60 |
** ci: "cherrypick-in". Like "mi" except for cherrypick merges. |
|
30aec47…
|
drh
|
61 |
** omitted if there are no cherrypick merges. |
|
68635d6…
|
drh
|
62 |
** h: The artifact hash of the object being graphed |
|
6908832…
|
drh
|
63 |
*/ |
|
6908832…
|
drh
|
64 |
/* The amendCss() function does a one-time change to the CSS to account |
|
6908832…
|
drh
|
65 |
** for the "circleNodes" and "showArrowheads" settings. Do this change |
|
6908832…
|
drh
|
66 |
** only once, even if there are multiple graphs being rendered. |
|
68635d6…
|
drh
|
67 |
*/ |
|
68635d6…
|
drh
|
68 |
var amendCssOnce = 1; // Only change the CSS one time |
|
68635d6…
|
drh
|
69 |
function amendCss(circleNodes,showArrowheads){ |
|
68635d6…
|
drh
|
70 |
if( !amendCssOnce ) return; |
|
68635d6…
|
drh
|
71 |
var css = ""; |
|
68635d6…
|
drh
|
72 |
if( circleNodes ){ |
|
68635d6…
|
drh
|
73 |
css += ".tl-node, .tl-node:after { border-radius: 50%; }"; |
|
68635d6…
|
drh
|
74 |
} |
|
561fa8a…
|
drh
|
75 |
if( !showArrowheads ){ |
|
68635d6…
|
drh
|
76 |
css += ".tl-arrow.u { display: none; }"; |
|
68635d6…
|
drh
|
77 |
} |
|
68635d6…
|
drh
|
78 |
if( css!=="" ){ |
|
68635d6…
|
drh
|
79 |
var style = document.createElement("style"); |
|
68635d6…
|
drh
|
80 |
style.textContent = css; |
|
68635d6…
|
drh
|
81 |
document.querySelector("head").appendChild(style); |
|
68635d6…
|
drh
|
82 |
} |
|
68635d6…
|
drh
|
83 |
amendCssOnce = 0; |
|
68635d6…
|
drh
|
84 |
} |
|
68635d6…
|
drh
|
85 |
|
|
6908832…
|
drh
|
86 |
/* The <span> object that holds the tooltip */ |
|
6908832…
|
drh
|
87 |
var tooltipObj = document.createElement("span"); |
|
6908832…
|
drh
|
88 |
tooltipObj.className = "tl-tooltip"; |
|
6908832…
|
drh
|
89 |
tooltipObj.style.display = "none"; |
|
6908832…
|
drh
|
90 |
document.getElementsByClassName("content")[0].appendChild(tooltipObj); |
|
55f56e9…
|
drh
|
91 |
tooltipObj.onmouseenter = function(){ |
|
55f56e9…
|
drh
|
92 |
/* Hold the tooltip constant as long as the mouse is over the tooltip. |
|
55f56e9…
|
drh
|
93 |
** In other words, do not let any of the timers changes the tooltip while |
|
55f56e9…
|
drh
|
94 |
** the mouse is directly over the tooltip. This makes it easier for the |
|
55f56e9…
|
drh
|
95 |
** user to move over top of the "copy-button" or the hyperlink to the |
|
55f56e9…
|
drh
|
96 |
** /info page. */ |
|
55f56e9…
|
drh
|
97 |
stopCloseTimer(); |
|
55f56e9…
|
drh
|
98 |
stopDwellTimer(); |
|
55f56e9…
|
drh
|
99 |
tooltipInfo.ixHover = tooltipInfo.ixActive; |
|
55f56e9…
|
drh
|
100 |
} |
|
6908832…
|
drh
|
101 |
tooltipObj.onmouseleave = function(){ |
|
6908832…
|
drh
|
102 |
if (tooltipInfo.ixActive != -1) resumeCloseTimer(); |
|
6908832…
|
drh
|
103 |
}; |
|
6908832…
|
drh
|
104 |
|
|
6908832…
|
drh
|
105 |
/* State information for the tooltip popup and its timers */ |
|
6908832…
|
drh
|
106 |
window.tooltipInfo = { |
|
6908832…
|
drh
|
107 |
dwellTimeout: 250, /* The tooltip dwell timeout. */ |
|
6908832…
|
drh
|
108 |
closeTimeout: 3000, /* The tooltip close timeout. */ |
|
6908832…
|
drh
|
109 |
hashDigits: 16, /* Limit of tooltip hashes ("hash-digits"). */ |
|
786d616…
|
drh
|
110 |
idTimer: 0, /* The tooltip dwell timer id */ |
|
786d616…
|
drh
|
111 |
idTimerClose: 0, /* The tooltip close timer id */ |
|
786d616…
|
drh
|
112 |
ixHover: -1, /* The mouse is over a thick riser arrow for |
|
786d616…
|
drh
|
113 |
** tx.rowinfo[ixHover]. Or -2 when the mouse is |
|
786d616…
|
drh
|
114 |
** over a graph node. Or -1 when the mouse is not |
|
786d616…
|
drh
|
115 |
** over anything. */ |
|
786d616…
|
drh
|
116 |
ixActive: -1, /* The item shown in the tooltip is tx.rowinfo[ixActive]. |
|
786d616…
|
drh
|
117 |
** ixActive is -1 if the tooltip is not visible */ |
|
6908832…
|
drh
|
118 |
nodeHover: null, /* Graph node under mouse when ixHover==-2 */ |
|
3a8abf4…
|
drh
|
119 |
idNodeActive: 0, /* Element ID of the graph node with the tooltip. */ |
|
6908832…
|
drh
|
120 |
posX: 0, posY: 0 /* The last mouse position. */ |
|
6908832…
|
drh
|
121 |
}; |
|
6908832…
|
drh
|
122 |
|
|
6908832…
|
drh
|
123 |
/* Functions used to control the tooltip popup and its timer */ |
|
786d616…
|
drh
|
124 |
function onKeyDown(event){ /* Hide the tooltip when ESC key pressed */ |
|
8ee5e55…
|
drh
|
125 |
var key = event.which || event.keyCode; |
|
8ee5e55…
|
drh
|
126 |
if( key==27 ){ |
|
8ee5e55…
|
drh
|
127 |
event.stopPropagation(); |
|
8ee5e55…
|
drh
|
128 |
hideGraphTooltip(); |
|
8ee5e55…
|
drh
|
129 |
} |
|
8ee5e55…
|
drh
|
130 |
} |
|
786d616…
|
drh
|
131 |
function hideGraphTooltip(){ /* Hide the tooltip */ |
|
8ee5e55…
|
drh
|
132 |
document.removeEventListener('keydown',onKeyDown,/* useCapture == */true); |
|
6908832…
|
drh
|
133 |
stopCloseTimer(); |
|
6908832…
|
drh
|
134 |
tooltipObj.style.display = "none"; |
|
6908832…
|
drh
|
135 |
tooltipInfo.ixActive = -1; |
|
3a8abf4…
|
drh
|
136 |
tooltipInfo.idNodeActive = 0; |
|
6908832…
|
drh
|
137 |
} |
|
f97a29d…
|
florian
|
138 |
window.onpagehide = hideGraphTooltip; |
|
6908832…
|
drh
|
139 |
function stopDwellTimer(){ |
|
786d616…
|
drh
|
140 |
if(tooltipInfo.idTimer!=0){ |
|
6908832…
|
drh
|
141 |
clearTimeout(tooltipInfo.idTimer); |
|
6908832…
|
drh
|
142 |
tooltipInfo.idTimer = 0; |
|
6908832…
|
drh
|
143 |
} |
|
6908832…
|
drh
|
144 |
} |
|
6908832…
|
drh
|
145 |
function resumeCloseTimer(){ |
|
6908832…
|
drh
|
146 |
/* This timer must be stopped explicitly to reset the elapsed timeout. */ |
|
786d616…
|
drh
|
147 |
if(tooltipInfo.idTimerClose==0 && tooltipInfo.closeTimeout>0) { |
|
6908832…
|
drh
|
148 |
tooltipInfo.idTimerClose = setTimeout(function(){ |
|
6908832…
|
drh
|
149 |
tooltipInfo.idTimerClose = 0; |
|
6908832…
|
drh
|
150 |
hideGraphTooltip(); |
|
6908832…
|
drh
|
151 |
},tooltipInfo.closeTimeout); |
|
6908832…
|
drh
|
152 |
} |
|
6908832…
|
drh
|
153 |
} |
|
6908832…
|
drh
|
154 |
function stopCloseTimer(){ |
|
786d616…
|
drh
|
155 |
if(tooltipInfo.idTimerClose!=0){ |
|
6908832…
|
drh
|
156 |
clearTimeout(tooltipInfo.idTimerClose); |
|
6908832…
|
drh
|
157 |
tooltipInfo.idTimerClose = 0; |
|
6908832…
|
drh
|
158 |
} |
|
6908832…
|
drh
|
159 |
} |
|
6908832…
|
drh
|
160 |
|
|
6908832…
|
drh
|
161 |
/* Construct that graph corresponding to the timeline-data-N object that |
|
6908832…
|
drh
|
162 |
** is passed in by the tx parameter */ |
|
68635d6…
|
drh
|
163 |
function TimelineGraph(tx){ |
|
68635d6…
|
drh
|
164 |
var topObj = document.getElementById("timelineTable"+tx.iTableId); |
|
68635d6…
|
drh
|
165 |
amendCss(tx.circleNodes, tx.showArrowheads); |
|
6908832…
|
drh
|
166 |
tooltipInfo.dwellTimeout = tx.dwellTimeout |
|
6908832…
|
drh
|
167 |
tooltipInfo.closeTimeout = tx.closeTimeout |
|
6908832…
|
drh
|
168 |
tooltipInfo.hashDigits = tx.hashDigits |
|
6908832…
|
drh
|
169 |
topObj.onclick = clickOnGraph |
|
6908832…
|
drh
|
170 |
topObj.ondblclick = dblclickOnGraph |
|
6908832…
|
drh
|
171 |
topObj.onmousemove = function(e) { |
|
6908832…
|
drh
|
172 |
var ix = findTxIndex(e); |
|
6908832…
|
drh
|
173 |
topObj.style.cursor = (ix<0) ? "" : "pointer" |
|
3a8abf4…
|
drh
|
174 |
mouseOverGraph(e,ix,null); |
|
6908832…
|
drh
|
175 |
}; |
|
6908832…
|
drh
|
176 |
topObj.onmouseleave = function(e) { |
|
6908832…
|
drh
|
177 |
/* Hide the tooltip if the mouse is outside the "timelineTableN" element, |
|
6908832…
|
drh
|
178 |
** and outside the tooltip. */ |
|
6908832…
|
drh
|
179 |
if(e.relatedTarget && e.relatedTarget != tooltipObj){ |
|
6908832…
|
drh
|
180 |
tooltipInfo.ixHover = -1; |
|
6908832…
|
drh
|
181 |
hideGraphTooltip(); |
|
6908832…
|
drh
|
182 |
stopDwellTimer(); |
|
6908832…
|
drh
|
183 |
stopCloseTimer(); |
|
6908832…
|
drh
|
184 |
} |
|
6908832…
|
drh
|
185 |
}; |
|
786d616…
|
drh
|
186 |
function mouseOverNode(e){ /* Invoked by mousemove events over a graph node */ |
|
6908832…
|
drh
|
187 |
e.stopPropagation() |
|
3a8abf4…
|
drh
|
188 |
mouseOverGraph(e,-2,this) |
|
3a8abf4…
|
drh
|
189 |
} |
|
3a8abf4…
|
drh
|
190 |
/* Combined mousemove handler for graph nodes and rails. */ |
|
3a8abf4…
|
drh
|
191 |
function mouseOverGraph(e,ix,node){ |
|
3a8abf4…
|
drh
|
192 |
stopDwellTimer(); // Mouse movement: reset the dwell timer. |
|
3a8abf4…
|
drh
|
193 |
var ownTooltip = // Check if the hovered element already has the tooltip. |
|
3a8abf4…
|
drh
|
194 |
(ix>=0 && ix==tooltipInfo.ixActive) || |
|
3a8abf4…
|
drh
|
195 |
(ix==-2 && tooltipInfo.idNodeActive==node.id); |
|
3a8abf4…
|
drh
|
196 |
if(ownTooltip) stopCloseTimer(); // ownTooltip: clear the close timer. |
|
3a8abf4…
|
drh
|
197 |
else resumeCloseTimer(); // !ownTooltip: resume the close timer. |
|
3a8abf4…
|
drh
|
198 |
tooltipInfo.ixHover = ix; |
|
3a8abf4…
|
drh
|
199 |
tooltipInfo.nodeHover = node; |
|
3a8abf4…
|
drh
|
200 |
tooltipInfo.posX = e.clientX; |
|
3a8abf4…
|
drh
|
201 |
tooltipInfo.posY = e.clientY; |
|
3a8abf4…
|
drh
|
202 |
if(ix!=-1 && !ownTooltip && tooltipInfo.dwellTimeout>0){ // Go dwell timer. |
|
3a8abf4…
|
drh
|
203 |
tooltipInfo.idTimer = setTimeout(function(){ |
|
6908832…
|
drh
|
204 |
tooltipInfo.idTimer = 0; |
|
6908832…
|
drh
|
205 |
stopCloseTimer(); |
|
6908832…
|
drh
|
206 |
showGraphTooltip(); |
|
6908832…
|
drh
|
207 |
},tooltipInfo.dwellTimeout); |
|
6908832…
|
drh
|
208 |
} |
|
6908832…
|
drh
|
209 |
} |
|
68635d6…
|
drh
|
210 |
var canvasDiv; |
|
68635d6…
|
drh
|
211 |
var railPitch; |
|
68635d6…
|
drh
|
212 |
var mergeOffset; |
|
68635d6…
|
drh
|
213 |
var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine; |
|
6908832…
|
drh
|
214 |
|
|
68635d6…
|
drh
|
215 |
function initGraph(){ |
|
68635d6…
|
drh
|
216 |
var parent = topObj.rows[0].cells[1]; |
|
68635d6…
|
drh
|
217 |
parent.style.verticalAlign = "top"; |
|
68635d6…
|
drh
|
218 |
canvasDiv = document.createElement("div"); |
|
68635d6…
|
drh
|
219 |
canvasDiv.className = "tl-canvas"; |
|
68635d6…
|
drh
|
220 |
canvasDiv.style.position = "absolute"; |
|
68635d6…
|
drh
|
221 |
parent.appendChild(canvasDiv); |
|
68635d6…
|
drh
|
222 |
|
|
68635d6…
|
drh
|
223 |
var elems = {}; |
|
68635d6…
|
drh
|
224 |
var elemClasses = [ |
|
68635d6…
|
drh
|
225 |
"rail", "mergeoffset", "node", "arrow u", "arrow u sm", "line", |
|
55ab522…
|
drh
|
226 |
"arrow merge r", "line merge", "arrow warp", "line warp", |
|
01d8bf9…
|
drh
|
227 |
"line cherrypick", "line dotted" |
|
68635d6…
|
drh
|
228 |
]; |
|
68635d6…
|
drh
|
229 |
for( var i=0; i<elemClasses.length; i++ ){ |
|
68635d6…
|
drh
|
230 |
var cls = elemClasses[i]; |
|
68635d6…
|
drh
|
231 |
var elem = document.createElement("div"); |
|
68635d6…
|
drh
|
232 |
elem.className = "tl-" + cls; |
|
68635d6…
|
drh
|
233 |
if( cls.indexOf("line")==0 ) elem.className += " v"; |
|
68635d6…
|
drh
|
234 |
canvasDiv.appendChild(elem); |
|
68635d6…
|
drh
|
235 |
var k = cls.replace(/\s/g, "_"); |
|
68635d6…
|
drh
|
236 |
var r = elem.getBoundingClientRect(); |
|
68635d6…
|
drh
|
237 |
var w = Math.round(r.right - r.left); |
|
68635d6…
|
drh
|
238 |
var h = Math.round(r.bottom - r.top); |
|
68635d6…
|
drh
|
239 |
elems[k] = {w: w, h: h, cls: cls}; |
|
68635d6…
|
drh
|
240 |
} |
|
68635d6…
|
drh
|
241 |
node = elems.node; |
|
68635d6…
|
drh
|
242 |
arrow = elems.arrow_u; |
|
68635d6…
|
drh
|
243 |
arrowSmall = elems.arrow_u_sm; |
|
68635d6…
|
drh
|
244 |
line = elems.line; |
|
68635d6…
|
drh
|
245 |
mArrow = elems.arrow_merge_r; |
|
68635d6…
|
drh
|
246 |
mLine = elems.line_merge; |
|
55ab522…
|
drh
|
247 |
cpLine = elems.line_cherrypick; |
|
68635d6…
|
drh
|
248 |
wArrow = elems.arrow_warp; |
|
68635d6…
|
drh
|
249 |
wLine = elems.line_warp; |
|
01d8bf9…
|
drh
|
250 |
dotLine = elems.line_dotted; |
|
68635d6…
|
drh
|
251 |
|
|
68635d6…
|
drh
|
252 |
var minRailPitch = Math.ceil((node.w+line.w)/2 + mArrow.w + 1); |
|
e63da90…
|
drh
|
253 |
if( window.innerWidth<400 ){ |
|
e63da90…
|
drh
|
254 |
railPitch = minRailPitch; |
|
68635d6…
|
drh
|
255 |
}else{ |
|
e63da90…
|
drh
|
256 |
if( tx.iRailPitch>0 ){ |
|
e63da90…
|
drh
|
257 |
railPitch = tx.iRailPitch; |
|
e63da90…
|
drh
|
258 |
}else{ |
|
e63da90…
|
drh
|
259 |
railPitch = elems.rail.w; |
|
e63da90…
|
drh
|
260 |
railPitch -= Math.floor((tx.nrail-1)*(railPitch-minRailPitch)/21); |
|
e63da90…
|
drh
|
261 |
} |
|
e63da90…
|
drh
|
262 |
railPitch = Math.max(railPitch, minRailPitch); |
|
68635d6…
|
drh
|
263 |
} |
|
68635d6…
|
drh
|
264 |
|
|
68635d6…
|
drh
|
265 |
if( tx.nomo ){ |
|
68635d6…
|
drh
|
266 |
mergeOffset = 0; |
|
68635d6…
|
drh
|
267 |
}else{ |
|
68635d6…
|
drh
|
268 |
mergeOffset = railPitch-minRailPitch-mLine.w; |
|
68635d6…
|
drh
|
269 |
mergeOffset = Math.min(mergeOffset, elems.mergeoffset.w); |
|
68635d6…
|
drh
|
270 |
mergeOffset = mergeOffset>0 ? mergeOffset + line.w/2 : 0; |
|
68635d6…
|
drh
|
271 |
} |
|
68635d6…
|
drh
|
272 |
|
|
68635d6…
|
drh
|
273 |
var canvasWidth = (tx.nrail-1)*railPitch + node.w; |
|
68635d6…
|
drh
|
274 |
canvasDiv.style.width = canvasWidth + "px"; |
|
68635d6…
|
drh
|
275 |
canvasDiv.style.position = "relative"; |
|
68635d6…
|
drh
|
276 |
} |
|
68635d6…
|
drh
|
277 |
function drawBox(cls,color,x0,y0,x1,y1){ |
|
68635d6…
|
drh
|
278 |
var n = document.createElement("div"); |
|
68635d6…
|
drh
|
279 |
x0 = Math.floor(x0); |
|
68635d6…
|
drh
|
280 |
y0 = Math.floor(y0); |
|
68635d6…
|
drh
|
281 |
x1 = x1 || x1===0 ? Math.floor(x1) : x0; |
|
68635d6…
|
drh
|
282 |
y1 = y1 || y1===0 ? Math.floor(y1) : y0; |
|
68635d6…
|
drh
|
283 |
if( x0>x1 ){ var t=x0; x0=x1; x1=t; } |
|
68635d6…
|
drh
|
284 |
if( y0>y1 ){ var t=y0; y0=y1; y1=t; } |
|
68635d6…
|
drh
|
285 |
var w = x1-x0; |
|
68635d6…
|
drh
|
286 |
var h = y1-y0; |
|
68635d6…
|
drh
|
287 |
n.style.position = "absolute"; |
|
68635d6…
|
drh
|
288 |
n.style.left = x0+"px"; |
|
68635d6…
|
drh
|
289 |
n.style.top = y0+"px"; |
|
68635d6…
|
drh
|
290 |
if( w ) n.style.width = w+"px"; |
|
68635d6…
|
drh
|
291 |
if( h ) n.style.height = h+"px"; |
|
68635d6…
|
drh
|
292 |
if( color ) n.style.backgroundColor = color; |
|
68635d6…
|
drh
|
293 |
n.className = "tl-"+cls; |
|
68635d6…
|
drh
|
294 |
canvasDiv.appendChild(n); |
|
68635d6…
|
drh
|
295 |
return n; |
|
68635d6…
|
drh
|
296 |
} |
|
68635d6…
|
drh
|
297 |
function absoluteY(obj){ |
|
6908832…
|
drh
|
298 |
var y = 0; |
|
6908832…
|
drh
|
299 |
do{ |
|
6908832…
|
drh
|
300 |
y += obj.offsetTop; |
|
6908832…
|
drh
|
301 |
}while( obj = obj.offsetParent ); |
|
6908832…
|
drh
|
302 |
return y; |
|
6908832…
|
drh
|
303 |
} |
|
6908832…
|
drh
|
304 |
function absoluteX(obj){ |
|
6908832…
|
drh
|
305 |
var x = 0; |
|
6908832…
|
drh
|
306 |
do{ |
|
6908832…
|
drh
|
307 |
x += obj.offsetLeft; |
|
6908832…
|
drh
|
308 |
}while( obj = obj.offsetParent ); |
|
6908832…
|
drh
|
309 |
return x; |
|
68635d6…
|
drh
|
310 |
} |
|
68635d6…
|
drh
|
311 |
function miLineY(p){ |
|
68635d6…
|
drh
|
312 |
return p.y + node.h - mLine.w - 1; |
|
68635d6…
|
drh
|
313 |
} |
|
68635d6…
|
drh
|
314 |
function drawLine(elem,color,x0,y0,x1,y1){ |
|
68635d6…
|
drh
|
315 |
var cls = elem.cls + " "; |
|
68635d6…
|
drh
|
316 |
if( x1===null ){ |
|
68635d6…
|
drh
|
317 |
x1 = x0+elem.w; |
|
68635d6…
|
drh
|
318 |
cls += "v"; |
|
68635d6…
|
drh
|
319 |
}else{ |
|
68635d6…
|
drh
|
320 |
y1 = y0+elem.w; |
|
68635d6…
|
drh
|
321 |
cls += "h"; |
|
68635d6…
|
drh
|
322 |
} |
|
d14590d…
|
drh
|
323 |
return drawBox(cls,color,x0,y0,x1,y1); |
|
68635d6…
|
drh
|
324 |
} |
|
6908832…
|
drh
|
325 |
function drawUpArrow(from,to,color,id){ |
|
68635d6…
|
drh
|
326 |
var y = to.y + node.h; |
|
68635d6…
|
drh
|
327 |
var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0); |
|
68635d6…
|
drh
|
328 |
var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow; |
|
68635d6…
|
drh
|
329 |
var x = to.x + (node.w-line.w)/2; |
|
68635d6…
|
drh
|
330 |
var y0 = from.y + node.h/2; |
|
68635d6…
|
drh
|
331 |
var y1 = Math.ceil(to.y + node.h + arw.h/2); |
|
6908832…
|
drh
|
332 |
var n = drawLine(line,color,x,y0,null,y1); |
|
6908832…
|
drh
|
333 |
addToolTip(n,id) |
|
68635d6…
|
drh
|
334 |
x = to.x + (node.w-arw.w)/2; |
|
6908832…
|
drh
|
335 |
n = drawBox(arw.cls,null,x,y); |
|
e59a7fd…
|
drh
|
336 |
if(color) n.style.borderBottomColor = color; |
|
6908832…
|
drh
|
337 |
addToolTip(n,id) |
|
01d8bf9…
|
drh
|
338 |
} |
|
6908832…
|
drh
|
339 |
function drawDotted(from,to,color,id){ |
|
01d8bf9…
|
drh
|
340 |
var x = to.x + (node.w-line.w)/2; |
|
01d8bf9…
|
drh
|
341 |
var y0 = from.y + node.h/2; |
|
01d8bf9…
|
drh
|
342 |
var y1 = Math.ceil(to.y + node.h); |
|
d14590d…
|
drh
|
343 |
var n = drawLine(dotLine,null,x,y0,null,y1) |
|
d14590d…
|
drh
|
344 |
if( color ) n.style.borderColor = color |
|
6908832…
|
drh
|
345 |
addToolTip(n,id) |
|
6908832…
|
drh
|
346 |
} |
|
6908832…
|
drh
|
347 |
function addToolTip(n,id){ |
|
6908832…
|
drh
|
348 |
if( id ) n.setAttribute("data-ix",id-tx.iTopRow) |
|
68635d6…
|
drh
|
349 |
} |
|
55ab522…
|
drh
|
350 |
/* Draw thin horizontal or vertical lines representing merges */ |
|
68635d6…
|
drh
|
351 |
function drawMergeLine(x0,y0,x1,y1){ |
|
68635d6…
|
drh
|
352 |
drawLine(mLine,null,x0,y0,x1,y1); |
|
68635d6…
|
drh
|
353 |
} |
|
55ab522…
|
drh
|
354 |
function drawCherrypickLine(x0,y0,x1,y1){ |
|
55ab522…
|
drh
|
355 |
drawLine(cpLine,null,x0,y0,x1,y1); |
|
55ab522…
|
drh
|
356 |
} |
|
55ab522…
|
drh
|
357 |
/* Draw an arrow representing an in-bound merge from the "rail"-th rail |
|
680837f…
|
drh
|
358 |
** over to the node of "p". Make it a checkpoint merge is "isCP" is true */ |
|
55ab522…
|
drh
|
359 |
function drawMergeArrow(p,rail,isCP){ |
|
68635d6…
|
drh
|
360 |
var x0 = rail*railPitch + node.w/2; |
|
68635d6…
|
drh
|
361 |
if( rail in mergeLines ){ |
|
68635d6…
|
drh
|
362 |
x0 += mergeLines[rail]; |
|
68635d6…
|
drh
|
363 |
if( p.r<rail ) x0 += mLine.w; |
|
68635d6…
|
drh
|
364 |
}else{ |
|
68635d6…
|
drh
|
365 |
x0 += (p.r<rail ? -1 : 1)*line.w/2; |
|
68635d6…
|
drh
|
366 |
} |
|
68635d6…
|
drh
|
367 |
var x1 = mArrow.w ? mArrow.w/2 : -node.w/2; |
|
68635d6…
|
drh
|
368 |
x1 = p.x + (p.r<rail ? node.w + Math.ceil(x1) : -x1); |
|
68635d6…
|
drh
|
369 |
var y = miLineY(p); |
|
68635d6…
|
drh
|
370 |
var x = p.x + (p.r<rail ? node.w : -mArrow.w); |
|
55ab522…
|
drh
|
371 |
var cls; |
|
55ab522…
|
drh
|
372 |
if( isCP ){ |
|
55ab522…
|
drh
|
373 |
drawCherrypickLine(x0,y,x1,null); |
|
55ab522…
|
drh
|
374 |
cls = "arrow cherrypick " + (p.r<rail ? "l" : "r"); |
|
55ab522…
|
drh
|
375 |
}else{ |
|
55ab522…
|
drh
|
376 |
drawMergeLine(x0,y,x1,null); |
|
55ab522…
|
drh
|
377 |
cls = "arrow merge " + (p.r<rail ? "l" : "r"); |
|
55ab522…
|
drh
|
378 |
} |
|
68635d6…
|
drh
|
379 |
drawBox(cls,null,x,y+(mLine.w-mArrow.h)/2); |
|
68635d6…
|
drh
|
380 |
} |
|
68635d6…
|
drh
|
381 |
function drawNode(p, btm){ |
|
5a6fe06…
|
drh
|
382 |
if( p.bg ){ |
|
5a6fe06…
|
drh
|
383 |
var e = document.getElementById("mc"+p.id); |
|
5a6fe06…
|
drh
|
384 |
if(e) e.style.backgroundColor = p.bg; |
|
5a6fe06…
|
drh
|
385 |
e = document.getElementById("md"+p.id); |
|
5a6fe06…
|
drh
|
386 |
if(e) e.style.backgroundColor = p.bg; |
|
5a6fe06…
|
drh
|
387 |
} |
|
694e11a…
|
drh
|
388 |
if( p.r<0 ) return; |
|
6908832…
|
drh
|
389 |
if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg,p.id); |
|
6908832…
|
drh
|
390 |
if( p.sb>0 ) drawDotted(p,tx.rowinfo[p.sb-tx.iTopRow],p.fg,p.id); |
|
68635d6…
|
drh
|
391 |
var cls = node.cls; |
|
c0a5083…
|
drh
|
392 |
if( p.hasOwnProperty('mi') && p.mi.length ) cls += " merge"; |
|
57bea36…
|
drh
|
393 |
if( p.f&2 ) cls += " closed-leaf"; |
|
57bea36…
|
drh
|
394 |
else if( p.f&1 ) cls += " leaf"; |
|
68635d6…
|
drh
|
395 |
var n = drawBox(cls,p.bg,p.x,p.y); |
|
68635d6…
|
drh
|
396 |
n.id = "tln"+p.id; |
|
68635d6…
|
drh
|
397 |
n.onclick = clickOnNode; |
|
6908832…
|
drh
|
398 |
n.ondblclick = dblclickOnNode; |
|
786d616…
|
drh
|
399 |
n.onmousemove = mouseOverNode; |
|
68635d6…
|
drh
|
400 |
n.style.zIndex = 10; |
|
57bea36…
|
drh
|
401 |
if( p.f&2 ){ |
|
57bea36…
|
drh
|
402 |
var pt1 = 0; |
|
57bea36…
|
drh
|
403 |
var pt2 = 100; |
|
57bea36…
|
drh
|
404 |
if( tx.circleNodes ){ |
|
57bea36…
|
drh
|
405 |
pt1 = 14; |
|
57bea36…
|
drh
|
406 |
pt2 = 86; |
|
57bea36…
|
drh
|
407 |
} |
|
57bea36…
|
drh
|
408 |
n.innerHTML = "<svg width='100%' height='100%'viewbox='0 0 100 100'>" |
|
57bea36…
|
drh
|
409 |
+ `<path d='M ${pt1},${pt1} L ${pt2},${pt2} M ${pt1},${pt2} L ${pt2},${pt1}'` |
|
57bea36…
|
drh
|
410 |
+ " stroke='currentcolor' stroke-width='13'/>" |
|
57bea36…
|
drh
|
411 |
+ "</svg>"; |
|
57bea36…
|
drh
|
412 |
} |
|
68635d6…
|
drh
|
413 |
if( !tx.omitDescenders ){ |
|
d14590d…
|
drh
|
414 |
if( p.u==0 ){ |
|
d14590d…
|
drh
|
415 |
if( p.hasOwnProperty('mo') && p.r==p.mo ){ |
|
d14590d…
|
drh
|
416 |
var ix = p.hasOwnProperty('cu') ? p.cu : p.mu; |
|
d14590d…
|
drh
|
417 |
var top = tx.rowinfo[ix-tx.iTopRow] |
|
6908832…
|
drh
|
418 |
drawUpArrow(p,{x: p.x, y: top.y-node.h}, p.fg, p.id); |
|
d14590d…
|
drh
|
419 |
}else if( p.y>100 ){ |
|
6908832…
|
drh
|
420 |
drawUpArrow(p,{x: p.x, y: p.y-50}, p.fg, p.id); |
|
d14590d…
|
drh
|
421 |
}else{ |
|
6908832…
|
drh
|
422 |
drawUpArrow(p,{x: p.x, y: 0},p.fg, p.id); |
|
d14590d…
|
drh
|
423 |
} |
|
d14590d…
|
drh
|
424 |
} |
|
d14590d…
|
drh
|
425 |
if( p.hasOwnProperty('d') ){ |
|
d14590d…
|
drh
|
426 |
if( p.y + 150 >= btm ){ |
|
6908832…
|
drh
|
427 |
drawUpArrow({x: p.x, y: btm - node.h/2},p,p.fg,p.id); |
|
d14590d…
|
drh
|
428 |
}else{ |
|
6908832…
|
drh
|
429 |
drawUpArrow({x: p.x, y: p.y+50},p,p.fg,p.id); |
|
6908832…
|
drh
|
430 |
drawDotted({x: p.x, y: p.y+63},{x: p.x, y: p.y+50-node.h/2},p.fg,p.id); |
|
d14590d…
|
drh
|
431 |
} |
|
d14590d…
|
drh
|
432 |
} |
|
68635d6…
|
drh
|
433 |
} |
|
55ab522…
|
drh
|
434 |
if( p.hasOwnProperty('mo') ){ |
|
68635d6…
|
drh
|
435 |
var x0 = p.x + node.w/2; |
|
68635d6…
|
drh
|
436 |
var x1 = p.mo*railPitch + node.w/2; |
|
68635d6…
|
drh
|
437 |
var u = tx.rowinfo[p.mu-tx.iTopRow]; |
|
632d07c…
|
drh
|
438 |
var mtop = u; |
|
632d07c…
|
drh
|
439 |
if( p.hasOwnProperty('cu') ){ |
|
632d07c…
|
drh
|
440 |
mtop = tx.rowinfo[p.cu-tx.iTopRow]; |
|
632d07c…
|
drh
|
441 |
} |
|
68635d6…
|
drh
|
442 |
var y1 = miLineY(u); |
|
d14590d…
|
drh
|
443 |
if( p.u<=0 || p.mo!=p.r ){ |
|
d14590d…
|
drh
|
444 |
if( p.u==0 && p.mo==p.r ){ |
|
632d07c…
|
drh
|
445 |
mergeLines[p.mo] = mtop.r<p.r ? -mergeOffset-mLine.w : mergeOffset; |
|
d14590d…
|
drh
|
446 |
}else{ |
|
d14590d…
|
drh
|
447 |
mergeLines[p.mo] = -mLine.w/2; |
|
d14590d…
|
drh
|
448 |
} |
|
d14590d…
|
drh
|
449 |
x1 += mergeLines[p.mo] |
|
68635d6…
|
drh
|
450 |
var y0 = p.y+2; |
|
1eb9f5a…
|
drh
|
451 |
var isCP = p.hasOwnProperty('cu'); |
|
30aec47…
|
drh
|
452 |
if( p.mu==p.id ){ |
|
146f02f…
|
drh
|
453 |
/* Special case: The merge riser already exists. Only draw the |
|
146f02f…
|
drh
|
454 |
/* horizontal line or arrow going from the node out to the riser. */ |
|
1eb9f5a…
|
drh
|
455 |
var dx = x1<x0 ? mArrow.w : -mArrow.w; |
|
1eb9f5a…
|
drh
|
456 |
if( isCP ){ |
|
1eb9f5a…
|
drh
|
457 |
drawCherrypickLine(x0,y0,x1+dx,null); |
|
1eb9f5a…
|
drh
|
458 |
cls = "arrow cherrypick " + (x1<x0 ? "l" : "r"); |
|
1eb9f5a…
|
drh
|
459 |
}else{ |
|
1eb9f5a…
|
drh
|
460 |
drawMergeLine(x0,y0,x1+dx,null); |
|
1eb9f5a…
|
drh
|
461 |
cls = "arrow merge " + (x1<x0 ? "l" : "r"); |
|
1eb9f5a…
|
drh
|
462 |
} |
|
7688673…
|
drh
|
463 |
if( !isCP || p.mu==p.cu ){ |
|
1eb9f5a…
|
drh
|
464 |
dx = x1<x0 ? mLine.w : -(mArrow.w + mLine.w/2); |
|
1eb9f5a…
|
drh
|
465 |
drawBox(cls,null,x1+dx,y0+(mLine.w-mArrow.h)/2); |
|
1eb9f5a…
|
drh
|
466 |
} |
|
30aec47…
|
drh
|
467 |
y1 = y0; |
|
30aec47…
|
drh
|
468 |
}else{ |
|
30aec47…
|
drh
|
469 |
drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null); |
|
30aec47…
|
drh
|
470 |
drawMergeLine(x1,y0+mLine.w,null,y1); |
|
30aec47…
|
drh
|
471 |
} |
|
146f02f…
|
drh
|
472 |
if( isCP && p.cu!=p.id ){ |
|
30aec47…
|
drh
|
473 |
var u2 = tx.rowinfo[p.cu-tx.iTopRow]; |
|
30aec47…
|
drh
|
474 |
var y2 = miLineY(u2); |
|
30aec47…
|
drh
|
475 |
drawCherrypickLine(x1,y1,null,y2); |
|
30aec47…
|
drh
|
476 |
} |
|
68635d6…
|
drh
|
477 |
}else if( mergeOffset ){ |
|
632d07c…
|
drh
|
478 |
mergeLines[p.mo] = mtop.r<p.r ? -mergeOffset-mLine.w : mergeOffset; |
|
68635d6…
|
drh
|
479 |
x1 += mergeLines[p.mo]; |
|
3a15daa…
|
drh
|
480 |
if( p.mu<p.id ){ |
|
30aec47…
|
drh
|
481 |
drawMergeLine(x1,p.y+node.h/2,null,y1); |
|
30aec47…
|
drh
|
482 |
} |
|
30aec47…
|
drh
|
483 |
if( p.hasOwnProperty('cu') ){ |
|
30aec47…
|
drh
|
484 |
var u2 = tx.rowinfo[p.cu-tx.iTopRow]; |
|
30aec47…
|
drh
|
485 |
var y2 = miLineY(u2); |
|
30aec47…
|
drh
|
486 |
drawCherrypickLine(x1,y1,null,y2); |
|
30aec47…
|
drh
|
487 |
} |
|
68635d6…
|
drh
|
488 |
}else{ |
|
68635d6…
|
drh
|
489 |
delete mergeLines[p.mo]; |
|
68635d6…
|
drh
|
490 |
} |
|
68635d6…
|
drh
|
491 |
} |
|
c0a5083…
|
drh
|
492 |
if( p.hasOwnProperty('au') ){ |
|
c0a5083…
|
drh
|
493 |
for( var i=0; i<p.au.length; i+=2 ){ |
|
c0a5083…
|
drh
|
494 |
var rail = p.au[i]; |
|
c0a5083…
|
drh
|
495 |
var x0 = p.x + node.w/2; |
|
c0a5083…
|
drh
|
496 |
var x1 = rail*railPitch + (node.w-line.w)/2; |
|
c0a5083…
|
drh
|
497 |
if( x0<x1 ){ |
|
c0a5083…
|
drh
|
498 |
x0 = Math.ceil(x0); |
|
c0a5083…
|
drh
|
499 |
x1 += line.w; |
|
c0a5083…
|
drh
|
500 |
} |
|
c0a5083…
|
drh
|
501 |
var y0 = p.y + (node.h-line.w)/2; |
|
c0a5083…
|
drh
|
502 |
var u = tx.rowinfo[p.au[i+1]-tx.iTopRow]; |
|
c0a5083…
|
drh
|
503 |
if( u.id<p.id ){ |
|
5399c5d…
|
drh
|
504 |
// normal thick up-arrow |
|
c0a5083…
|
drh
|
505 |
drawLine(line,u.fg,x0,y0,x1,null); |
|
6908832…
|
drh
|
506 |
drawUpArrow(p,u,u.fg,u.id); |
|
c0a5083…
|
drh
|
507 |
}else{ |
|
5399c5d…
|
drh
|
508 |
// timewarp: The child node occurs before the parent |
|
c0a5083…
|
drh
|
509 |
var y1 = u.y + (node.h-line.w)/2; |
|
5399c5d…
|
drh
|
510 |
var n = drawLine(wLine,u.fg,x0,y0,x1,null); |
|
5399c5d…
|
drh
|
511 |
addToolTip(n,u.id) |
|
5399c5d…
|
drh
|
512 |
n = drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w); |
|
5399c5d…
|
drh
|
513 |
addToolTip(n,u.id) |
|
5399c5d…
|
drh
|
514 |
n = drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null); |
|
5399c5d…
|
drh
|
515 |
addToolTip(n,u.id) |
|
c0a5083…
|
drh
|
516 |
var x = u.x-wArrow.w; |
|
c0a5083…
|
drh
|
517 |
var y = u.y+(node.h-wArrow.h)/2; |
|
5399c5d…
|
drh
|
518 |
n = drawBox(wArrow.cls,null,x,y); |
|
5399c5d…
|
drh
|
519 |
addToolTip(n,u.id) |
|
c0a5083…
|
drh
|
520 |
if( u.fg ) n.style.borderLeftColor = u.fg; |
|
c0a5083…
|
drh
|
521 |
} |
|
c0a5083…
|
drh
|
522 |
} |
|
c0a5083…
|
drh
|
523 |
} |
|
c0a5083…
|
drh
|
524 |
if( p.hasOwnProperty('mi') ){ |
|
c0a5083…
|
drh
|
525 |
for( var i=0; i<p.mi.length; i++ ){ |
|
c0a5083…
|
drh
|
526 |
var rail = p.mi[i]; |
|
c0a5083…
|
drh
|
527 |
if( rail<0 ){ |
|
2bbd70d…
|
drh
|
528 |
rail = -1-rail; |
|
55ab522…
|
drh
|
529 |
mergeLines[rail] = -mLine.w/2; |
|
55ab522…
|
drh
|
530 |
var x = rail*railPitch + (node.w-mLine.w)/2; |
|
30aec47…
|
drh
|
531 |
var y = miLineY(p); |
|
30aec47…
|
drh
|
532 |
drawMergeLine(x,y,null,mergeBtm[rail]); |
|
30aec47…
|
drh
|
533 |
mergeBtm[rail] = y; |
|
55ab522…
|
drh
|
534 |
} |
|
55ab522…
|
drh
|
535 |
drawMergeArrow(p,rail,0); |
|
55ab522…
|
drh
|
536 |
} |
|
55ab522…
|
drh
|
537 |
} |
|
30aec47…
|
drh
|
538 |
if( p.hasOwnProperty('ci') ){ |
|
30aec47…
|
drh
|
539 |
for( var i=0; i<p.ci.length; i++ ){ |
|
30aec47…
|
drh
|
540 |
var rail = p.ci[i]; |
|
55ab522…
|
drh
|
541 |
if( rail<0 ){ |
|
c0a5083…
|
drh
|
542 |
rail = -rail; |
|
c0a5083…
|
drh
|
543 |
mergeLines[rail] = -mLine.w/2; |
|
c0a5083…
|
drh
|
544 |
var x = rail*railPitch + (node.w-mLine.w)/2; |
|
30aec47…
|
drh
|
545 |
var y = miLineY(p); |
|
30aec47…
|
drh
|
546 |
drawCherrypickLine(x,y,null,mergeBtm[rail]); |
|
30aec47…
|
drh
|
547 |
mergeBtm[rail] = y; |
|
c0a5083…
|
drh
|
548 |
} |
|
55ab522…
|
drh
|
549 |
drawMergeArrow(p,rail,1); |
|
c0a5083…
|
drh
|
550 |
} |
|
68635d6…
|
drh
|
551 |
} |
|
68635d6…
|
drh
|
552 |
} |
|
68635d6…
|
drh
|
553 |
var mergeLines; |
|
30aec47…
|
drh
|
554 |
var mergeBtm = new Array; |
|
68635d6…
|
drh
|
555 |
function renderGraph(){ |
|
68635d6…
|
drh
|
556 |
mergeLines = {}; |
|
68635d6…
|
drh
|
557 |
canvasDiv.innerHTML = ""; |
|
68635d6…
|
drh
|
558 |
var canvasY = absoluteY(canvasDiv); |
|
68635d6…
|
drh
|
559 |
for(var i=0; i<tx.rowinfo.length; i++ ){ |
|
68635d6…
|
drh
|
560 |
var e = document.getElementById("m"+tx.rowinfo[i].id); |
|
68635d6…
|
drh
|
561 |
tx.rowinfo[i].y = absoluteY(e) - canvasY; |
|
68635d6…
|
drh
|
562 |
tx.rowinfo[i].x = tx.rowinfo[i].r*railPitch; |
|
68635d6…
|
drh
|
563 |
} |
|
8e2b8b0…
|
drh
|
564 |
var tlBtm = document.getElementById(tx.bottomRowId); |
|
68635d6…
|
drh
|
565 |
if( tlBtm.offsetHeight<node.h ){ |
|
68635d6…
|
drh
|
566 |
tlBtm.style.height = node.h + "px"; |
|
68635d6…
|
drh
|
567 |
} |
|
68635d6…
|
drh
|
568 |
var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight; |
|
30aec47…
|
drh
|
569 |
for( var i=0; i<tx.nrail; i++) mergeBtm[i] = btm; |
|
68635d6…
|
drh
|
570 |
for( var i=tx.rowinfo.length-1; i>=0; i-- ){ |
|
68635d6…
|
drh
|
571 |
drawNode(tx.rowinfo[i], btm); |
|
68635d6…
|
drh
|
572 |
} |
|
68635d6…
|
drh
|
573 |
} |
|
68635d6…
|
drh
|
574 |
var selRow; |
|
6908832…
|
drh
|
575 |
function clickOnNode(e){ |
|
6908832…
|
drh
|
576 |
hideGraphTooltip() |
|
68635d6…
|
drh
|
577 |
var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow]; |
|
68635d6…
|
drh
|
578 |
if( !selRow ){ |
|
68635d6…
|
drh
|
579 |
selRow = p; |
|
68635d6…
|
drh
|
580 |
this.className += " sel"; |
|
68635d6…
|
drh
|
581 |
canvasDiv.className += " sel"; |
|
68635d6…
|
drh
|
582 |
}else if( selRow==p ){ |
|
68635d6…
|
drh
|
583 |
selRow = null; |
|
68635d6…
|
drh
|
584 |
this.className = this.className.replace(" sel", ""); |
|
68635d6…
|
drh
|
585 |
canvasDiv.className = canvasDiv.className.replace(" sel", ""); |
|
68635d6…
|
drh
|
586 |
}else{ |
|
68635d6…
|
drh
|
587 |
if( tx.fileDiff ){ |
|
d96982f…
|
mgagnon
|
588 |
location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h; |
|
6908832…
|
drh
|
589 |
}else{ |
|
d96982f…
|
mgagnon
|
590 |
var href = tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h; |
|
d96982f…
|
mgagnon
|
591 |
let params = (new URL(document.location)).searchParams; |
|
262c0fb…
|
george
|
592 |
if(params && typeof params === "object"){ |
|
262c0fb…
|
george
|
593 |
/* When called from /timeline page, If chng=str was specified in the |
|
262c0fb…
|
george
|
594 |
** QueryString, specify glob=str on the /vdiff page */ |
|
262c0fb…
|
george
|
595 |
let glob = params.get("chng"); |
|
262c0fb…
|
george
|
596 |
if( !glob ){ |
|
262c0fb…
|
george
|
597 |
/* When called from /vdiff page, keep the glob= QueryString if |
|
262c0fb…
|
george
|
598 |
** present. */ |
|
262c0fb…
|
george
|
599 |
glob = params.get("glob"); |
|
262c0fb…
|
george
|
600 |
} |
|
262c0fb…
|
george
|
601 |
if( glob ){ |
|
262c0fb…
|
george
|
602 |
href += "&glob=" + glob; |
|
262c0fb…
|
george
|
603 |
} |
|
262c0fb…
|
george
|
604 |
} |
|
d96982f…
|
mgagnon
|
605 |
location.href = href; |
|
6908832…
|
drh
|
606 |
} |
|
6908832…
|
drh
|
607 |
} |
|
6908832…
|
drh
|
608 |
e.stopPropagation() |
|
6908832…
|
drh
|
609 |
} |
|
6908832…
|
drh
|
610 |
function dblclickOnNode(e){ |
|
6908832…
|
drh
|
611 |
var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow]; |
|
6908832…
|
drh
|
612 |
window.location.href = tx.baseUrl+"/info/"+p.h |
|
6908832…
|
drh
|
613 |
e.stopPropagation() |
|
6908832…
|
drh
|
614 |
} |
|
6908832…
|
drh
|
615 |
function findTxIndex(e){ |
|
8ee5e55…
|
drh
|
616 |
if( !tx.rowinfo ) return -1; |
|
6908832…
|
drh
|
617 |
/* Look at all the graph elements. If any graph elements that is near |
|
6908832…
|
drh
|
618 |
** the click-point "e" and has a "data-ix" attribute, then return |
|
6908832…
|
drh
|
619 |
** the value of that attribute. Otherwise return -1 */ |
|
6908832…
|
drh
|
620 |
var x = e.clientX + window.pageXOffset - absoluteX(canvasDiv); |
|
6908832…
|
drh
|
621 |
var y = e.clientY + window.pageYOffset - absoluteY(canvasDiv); |
|
6908832…
|
drh
|
622 |
var aNode = canvasDiv.childNodes |
|
6908832…
|
drh
|
623 |
var nNode = aNode.length; |
|
6908832…
|
drh
|
624 |
var i; |
|
6908832…
|
drh
|
625 |
for(i=0;i<nNode;i++){ |
|
6908832…
|
drh
|
626 |
var n = aNode[i] |
|
6908832…
|
drh
|
627 |
if( !n.hasAttribute("data-ix") ) continue; |
|
6908832…
|
drh
|
628 |
if( x<n.offsetLeft-5 ) continue; |
|
6908832…
|
drh
|
629 |
if( x>n.offsetLeft+n.offsetWidth+5 ) continue; |
|
6908832…
|
drh
|
630 |
if( y<n.offsetTop-5 ) continue; |
|
6908832…
|
drh
|
631 |
if( y>n.offsetTop+n.offsetHeight ) continue; |
|
6908832…
|
drh
|
632 |
return n.getAttribute("data-ix") |
|
6908832…
|
drh
|
633 |
} |
|
6908832…
|
drh
|
634 |
return -1 |
|
6908832…
|
drh
|
635 |
} |
|
6908832…
|
drh
|
636 |
/* Compute the hyperlink for the branch graph for tx.rowinfo[ix] */ |
|
6908832…
|
drh
|
637 |
function branchHyperlink(ix){ |
|
6908832…
|
drh
|
638 |
var br = tx.rowinfo[ix].br |
|
6908832…
|
drh
|
639 |
var dest = tx.baseUrl + "/timeline?r=" + encodeURIComponent(br) |
|
6908832…
|
drh
|
640 |
dest += tx.fileDiff ? "&m&cf=" : "&m&c=" |
|
6908832…
|
drh
|
641 |
dest += encodeURIComponent(tx.rowinfo[ix].h) |
|
6908832…
|
drh
|
642 |
return dest |
|
6908832…
|
drh
|
643 |
} |
|
6908832…
|
drh
|
644 |
function clickOnGraph(e){ |
|
3a8abf4…
|
drh
|
645 |
stopCloseTimer(); |
|
3a8abf4…
|
drh
|
646 |
stopDwellTimer(); |
|
6908832…
|
drh
|
647 |
tooltipInfo.ixHover = findTxIndex(e); |
|
6908832…
|
drh
|
648 |
tooltipInfo.posX = e.clientX; |
|
6908832…
|
drh
|
649 |
tooltipInfo.posY = e.clientY; |
|
6908832…
|
drh
|
650 |
showGraphTooltip(); |
|
6908832…
|
drh
|
651 |
} |
|
6908832…
|
drh
|
652 |
function showGraphTooltip(){ |
|
6908832…
|
drh
|
653 |
var html = null |
|
6908832…
|
drh
|
654 |
var ix = -1 |
|
6908832…
|
drh
|
655 |
if( tooltipInfo.ixHover==-2 ){ |
|
6908832…
|
drh
|
656 |
ix = parseInt(tooltipInfo.nodeHover.id.match(/\d+$/)[0],10)-tx.iTopRow |
|
6908832…
|
drh
|
657 |
var h = tx.rowinfo[ix].h |
|
6908832…
|
drh
|
658 |
var dest = tx.baseUrl + "/info/" + h |
|
6908832…
|
drh
|
659 |
h = h.slice(0,tooltipInfo.hashDigits); // Assume single-byte characters. |
|
6908832…
|
drh
|
660 |
if( tx.fileDiff ){ |
|
6908832…
|
drh
|
661 |
html = "artifact <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
|
6908832…
|
drh
|
662 |
}else{ |
|
6908832…
|
drh
|
663 |
html = "check-in <a id=\"tooltip-link\" href=\""+dest+"\">"+h+"</a>" |
|
6908832…
|
drh
|
664 |
} |
|
6908832…
|
drh
|
665 |
tooltipInfo.ixActive = -2; |
|
3a8abf4…
|
drh
|
666 |
tooltipInfo.idNodeActive = tooltipInfo.nodeHover.id; |
|
6908832…
|
drh
|
667 |
}else if( tooltipInfo.ixHover>=0 ){ |
|
6908832…
|
drh
|
668 |
ix = tooltipInfo.ixHover |
|
6908832…
|
drh
|
669 |
var br = tx.rowinfo[ix].br |
|
6908832…
|
drh
|
670 |
var dest = branchHyperlink(ix) |
|
6908832…
|
drh
|
671 |
var hbr = br.replace(/&/g, "&") |
|
6908832…
|
drh
|
672 |
.replace(/</g, "<") |
|
6908832…
|
drh
|
673 |
.replace(/>/g, ">") |
|
6908832…
|
drh
|
674 |
.replace(/"/g, """) |
|
6908832…
|
drh
|
675 |
.replace(/'/g, "'"); |
|
6908832…
|
drh
|
676 |
html = "branch <a id=\"tooltip-link\" href=\""+dest+"\">"+hbr+"</a>" |
|
6908832…
|
drh
|
677 |
tooltipInfo.ixActive = ix; |
|
3a8abf4…
|
drh
|
678 |
tooltipInfo.idNodeActive = 0; |
|
6908832…
|
drh
|
679 |
} |
|
6908832…
|
drh
|
680 |
if( html ){ |
|
6908832…
|
drh
|
681 |
/* Setup while hidden, to ensure proper dimensions. */ |
|
6908832…
|
drh
|
682 |
var s = getComputedStyle(document.body) |
|
6908832…
|
drh
|
683 |
if( tx.rowinfo[ix].bg.length ){ |
|
6908832…
|
drh
|
684 |
tooltipObj.style.backgroundColor = tx.rowinfo[ix].bg |
|
68635d6…
|
drh
|
685 |
}else{ |
|
6908832…
|
drh
|
686 |
tooltipObj.style.backgroundColor = s.getPropertyValue('background-color') |
|
68635d6…
|
drh
|
687 |
} |
|
6908832…
|
drh
|
688 |
tooltipObj.style.borderColor = |
|
6908832…
|
drh
|
689 |
tooltipObj.style.color = s.getPropertyValue('color') |
|
6908832…
|
drh
|
690 |
tooltipObj.style.visibility = "hidden" |
|
6908832…
|
drh
|
691 |
tooltipObj.innerHTML = html |
|
9a5169f…
|
drh
|
692 |
tooltipObj.insertBefore(makeCopyButton("tooltip-link",0,0), |
|
9a5169f…
|
drh
|
693 |
tooltipObj.childNodes[1]); |
|
6908832…
|
drh
|
694 |
tooltipObj.style.display = "inline" |
|
6908832…
|
drh
|
695 |
tooltipObj.style.position = "absolute" |
|
6908832…
|
drh
|
696 |
var x = tooltipInfo.posX + 4 + window.pageXOffset |
|
6908832…
|
drh
|
697 |
- absoluteX(tooltipObj.offsetParent) |
|
6908832…
|
drh
|
698 |
tooltipObj.style.left = x+"px" |
|
6908832…
|
drh
|
699 |
var y = tooltipInfo.posY + window.pageYOffset |
|
6908832…
|
drh
|
700 |
- tooltipObj.clientHeight - 4 |
|
6908832…
|
drh
|
701 |
- absoluteY(tooltipObj.offsetParent) |
|
6908832…
|
drh
|
702 |
tooltipObj.style.top = y+"px" |
|
6908832…
|
drh
|
703 |
tooltipObj.style.visibility = "visible" |
|
8ee5e55…
|
drh
|
704 |
document.addEventListener('keydown',onKeyDown,/* useCapture == */true); |
|
6908832…
|
drh
|
705 |
}else{ |
|
6908832…
|
drh
|
706 |
hideGraphTooltip() |
|
6908832…
|
drh
|
707 |
} |
|
6908832…
|
drh
|
708 |
} |
|
6908832…
|
drh
|
709 |
function dblclickOnGraph(e){ |
|
6908832…
|
drh
|
710 |
var ix = findTxIndex(e); |
|
6908832…
|
drh
|
711 |
hideGraphTooltip() |
|
6908832…
|
drh
|
712 |
if( ix>=0 ){ |
|
6908832…
|
drh
|
713 |
var dest = branchHyperlink(ix) |
|
6908832…
|
drh
|
714 |
window.location.href = dest |
|
68635d6…
|
drh
|
715 |
} |
|
68635d6…
|
drh
|
716 |
} |
|
68635d6…
|
drh
|
717 |
function changeDisplay(selector,value){ |
|
68635d6…
|
drh
|
718 |
var x = document.getElementsByClassName(selector); |
|
68635d6…
|
drh
|
719 |
var n = x.length; |
|
68635d6…
|
drh
|
720 |
for(var i=0; i<n; i++) {x[i].style.display = value;} |
|
68635d6…
|
drh
|
721 |
} |
|
68635d6…
|
drh
|
722 |
function changeDisplayById(id,value){ |
|
68635d6…
|
drh
|
723 |
var x = document.getElementById(id); |
|
68635d6…
|
drh
|
724 |
if(x) x.style.display=value; |
|
68635d6…
|
drh
|
725 |
} |
|
9a49859…
|
florian
|
726 |
function toggleDetail(evt){ |
|
b09a9b6…
|
florian
|
727 |
/* Ignore clicks to hyperlinks and other "click-responsive" HTML elements. |
|
b09a9b6…
|
florian
|
728 |
** This click-handler is set for <SPAN> elements with the CSS class names |
|
b09a9b6…
|
florian
|
729 |
** "timelineEllipsis" and "timelineCompactComment", which are part of the |
|
b09a9b6…
|
florian
|
730 |
** "Compact" and "Simple" views. */ |
|
b09a9b6…
|
florian
|
731 |
var xClickyHTML = /^(?:A|AREA|BUTTON|INPUT|LABEL|SELECT|TEXTAREA|DETAILS)$/; |
|
b09a9b6…
|
florian
|
732 |
if( xClickyHTML.test(evt.target.tagName) ) return; |
|
68635d6…
|
drh
|
733 |
var id = parseInt(this.getAttribute('data-id')) |
|
652267e…
|
drh
|
734 |
var x = document.getElementById("detail-"+id); |
|
68635d6…
|
drh
|
735 |
if( x.style.display=="inline" ){ |
|
68635d6…
|
drh
|
736 |
x.style.display="none"; |
|
6ce705b…
|
drh
|
737 |
document.getElementById("ellipsis-"+id).textContent = "..."; |
|
68635d6…
|
drh
|
738 |
changeDisplayById("links-"+id,"none"); |
|
68635d6…
|
drh
|
739 |
}else{ |
|
68635d6…
|
drh
|
740 |
x.style.display="inline"; |
|
6ce705b…
|
drh
|
741 |
document.getElementById("ellipsis-"+id).textContent = "←"; |
|
68635d6…
|
drh
|
742 |
changeDisplayById("links-"+id,"inline"); |
|
68635d6…
|
drh
|
743 |
} |
|
68635d6…
|
drh
|
744 |
checkHeight(); |
|
68635d6…
|
drh
|
745 |
} |
|
68635d6…
|
drh
|
746 |
function scrollToSelected(){ |
|
68635d6…
|
drh
|
747 |
var x = document.getElementsByClassName('timelineSelected'); |
|
68635d6…
|
drh
|
748 |
if(x[0]){ |
|
68635d6…
|
drh
|
749 |
var h = window.innerHeight; |
|
68635d6…
|
drh
|
750 |
var y = absoluteY(x[0]) - h/2; |
|
68635d6…
|
drh
|
751 |
if( y>0 ) window.scrollTo(0, y); |
|
68635d6…
|
drh
|
752 |
} |
|
68635d6…
|
drh
|
753 |
} |
|
0ac1895…
|
drh
|
754 |
if( tx.rowinfo ){ |
|
0ac1895…
|
drh
|
755 |
var lastRow = |
|
0ac1895…
|
drh
|
756 |
document.getElementById("m"+tx.rowinfo[tx.rowinfo.length-1].id); |
|
0ac1895…
|
drh
|
757 |
var lastY = 0; |
|
0ac1895…
|
drh
|
758 |
function checkHeight(){ |
|
0ac1895…
|
drh
|
759 |
var h = absoluteY(lastRow); |
|
0ac1895…
|
drh
|
760 |
if( h!=lastY ){ |
|
0ac1895…
|
drh
|
761 |
renderGraph(); |
|
0ac1895…
|
drh
|
762 |
lastY = h; |
|
0ac1895…
|
drh
|
763 |
} |
|
0ac1895…
|
drh
|
764 |
setTimeout(checkHeight, 1000); |
|
0ac1895…
|
drh
|
765 |
} |
|
0ac1895…
|
drh
|
766 |
initGraph(); |
|
0ac1895…
|
drh
|
767 |
checkHeight(); |
|
0ac1895…
|
drh
|
768 |
}else{ |
|
0ac1895…
|
drh
|
769 |
function checkHeight(){} |
|
0ac1895…
|
drh
|
770 |
} |
|
cee4bcf…
|
drh
|
771 |
if( tx.scrollToSelect ){ |
|
cee4bcf…
|
drh
|
772 |
scrollToSelected(); |
|
cee4bcf…
|
drh
|
773 |
} |
|
cee4bcf…
|
drh
|
774 |
|
|
6ce705b…
|
drh
|
775 |
/* Set the onclick= attributes for elements of the "Compact" and |
|
6ce705b…
|
drh
|
776 |
** "Simple" views so that clicking turns the details on and off. |
|
68635d6…
|
drh
|
777 |
*/ |
|
68635d6…
|
drh
|
778 |
var lx = topObj.getElementsByClassName('timelineEllipsis'); |
|
68635d6…
|
drh
|
779 |
var i; |
|
68635d6…
|
drh
|
780 |
for(i=0; i<lx.length; i++){ |
|
9a49859…
|
florian
|
781 |
if( lx[i].hasAttribute('data-id') ){ |
|
9a49859…
|
florian
|
782 |
lx[i].addEventListener('click',toggleDetail); |
|
9a49859…
|
florian
|
783 |
} |
|
68635d6…
|
drh
|
784 |
} |
|
68635d6…
|
drh
|
785 |
lx = topObj.getElementsByClassName('timelineCompactComment'); |
|
68635d6…
|
drh
|
786 |
for(i=0; i<lx.length; i++){ |
|
9a49859…
|
florian
|
787 |
if( lx[i].hasAttribute('data-id') ){ |
|
9a49859…
|
florian
|
788 |
lx[i].addEventListener('click',toggleDetail); |
|
9a49859…
|
florian
|
789 |
} |
|
8fc7c1b…
|
drh
|
790 |
} |
|
8fc7c1b…
|
drh
|
791 |
if( window.innerWidth<400 ){ |
|
8fc7c1b…
|
drh
|
792 |
/* On narrow displays, shift the date from the first column to the |
|
8fc7c1b…
|
drh
|
793 |
** third column, to make the first column narrower */ |
|
8fc7c1b…
|
drh
|
794 |
lx = topObj.getElementsByClassName('timelineDateRow'); |
|
8fc7c1b…
|
drh
|
795 |
for(i=0; i<lx.length; i++){ |
|
8fc7c1b…
|
drh
|
796 |
var rx = lx[i]; |
|
8fc7c1b…
|
drh
|
797 |
if( rx.getAttribute('data-reordered') ) break; |
|
8fc7c1b…
|
drh
|
798 |
rx.setAttribute('data-reordered',1); |
|
8fc7c1b…
|
drh
|
799 |
rx.appendChild(rx.firstChild); |
|
8fc7c1b…
|
drh
|
800 |
rx.insertBefore(rx.childNodes[1],rx.firstChild); |
|
4a1a474…
|
drh
|
801 |
} |
|
4a1a474…
|
drh
|
802 |
/* Do not show the HH:MM timestamps on very narrow displays |
|
4a1a474…
|
drh
|
803 |
** as they take up too much horizontal space. */ |
|
4a1a474…
|
drh
|
804 |
lx = topObj.getElementsByClassName('timelineHistLink'); |
|
4a1a474…
|
drh
|
805 |
for(i=0; i<lx.length; i++){ |
|
4a1a474…
|
drh
|
806 |
var rx = lx[i]; |
|
4a1a474…
|
drh
|
807 |
rx.style.display="none"; |
|
8fc7c1b…
|
drh
|
808 |
} |
|
68635d6…
|
drh
|
809 |
} |
|
68635d6…
|
drh
|
810 |
} |
|
68635d6…
|
drh
|
811 |
|
|
68635d6…
|
drh
|
812 |
/* Look for all timeline-data-NN objects. Load each one and draw |
|
68635d6…
|
drh
|
813 |
** a graph for each one. |
|
68635d6…
|
drh
|
814 |
*/ |
|
68635d6…
|
drh
|
815 |
(function(){ |
|
68635d6…
|
drh
|
816 |
var i; |
|
68635d6…
|
drh
|
817 |
for(i=0; 1; i++){ |
|
68635d6…
|
drh
|
818 |
var dataObj = document.getElementById("timeline-data-"+i); |
|
68635d6…
|
drh
|
819 |
if(!dataObj) break; |
|
68635d6…
|
drh
|
820 |
var txJson = dataObj.textContent || dataObj.innerText; |
|
68635d6…
|
drh
|
821 |
var tx = JSON.parse(txJson); |
|
0ac1895…
|
drh
|
822 |
TimelineGraph(tx); |
|
68635d6…
|
drh
|
823 |
} |
|
139db4c…
|
drh
|
824 |
}()); |