| | @@ -1,12 +1,13 @@ |
| 1 | 1 | /* This module contains javascript needed to render timeline graphs in Fossil. |
| 2 | 2 | ** |
| 3 | 3 | ** Prior to sourcing this script, there should be a separate |
| 4 | | -** <script type='application/json' id='timeline-data'> containing JSON |
| 5 | | -** like this: |
| 4 | +** <script type='application/json' id='timeline-data-NN'> for each graph, |
| 5 | +** each containing JSON like this: |
| 6 | 6 | ** |
| 7 | | -** { "circleNodes": BOOLEAN, // True for circle nodes. False for squares |
| 7 | +** { "iTableId": INTEGER, // Table sequence number (NN) |
| 8 | +** "circleNodes": BOOLEAN, // True for circle nodes. False for squares |
| 8 | 9 | ** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit |
| 9 | 10 | ** "iRailPitch": INTEGER, // Spacing between vertical lines (px) |
| 10 | 11 | ** "colorGraph": BOOLEAN, // True to put color on graph lines |
| 11 | 12 | ** "nomo": BOOLEAN, // True to join merge lines with rails |
| 12 | 13 | ** "iTopRow": INTEGER, // Index of top-most row in the graph |
| | @@ -45,315 +46,335 @@ |
| 45 | 46 | ** negative, then the rail position is the absolute value of mi[] |
| 46 | 47 | ** and a thin merge-arrow descender is drawn to the bottom of |
| 47 | 48 | ** the screen. |
| 48 | 49 | ** h: The artifact hash of the object being graphed |
| 49 | 50 | */ |
| 50 | | -var timelineObj = document.getElementById("timeline-data"); |
| 51 | | -var timelineJson = timelineObj.textContent || timelineObj.innerText; |
| 52 | | -var tx = JSON.parse(timelineJson); |
| 53 | | -var css = ""; |
| 54 | | -if( tx.circleNodes ){ |
| 55 | | - css += ".tl-node, .tl-node:after { border-radius: 50%; }"; |
| 56 | | -} |
| 57 | | -if( tx.showArrowheads ){ |
| 58 | | - css += ".tl-arrow.u { display: none; }"; |
| 59 | | -} |
| 60 | | -if( css!=="" ){ |
| 61 | | - var style = document.createElement("style"); |
| 62 | | - style.textContent = css; |
| 63 | | - document.querySelector("head").appendChild(style); |
| 64 | | -} |
| 65 | | -var canvasDiv; |
| 66 | | -var railPitch; |
| 67 | | -var mergeOffset; |
| 68 | | -var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine; |
| 69 | | -function initGraph(){ |
| 70 | | - var parent = document.getElementById("timelineTable").rows[0].cells[1]; |
| 71 | | - parent.style.verticalAlign = "top"; |
| 72 | | - canvasDiv = document.createElement("div"); |
| 73 | | - canvasDiv.className = "tl-canvas"; |
| 74 | | - canvasDiv.style.position = "absolute"; |
| 75 | | - parent.appendChild(canvasDiv); |
| 76 | | - |
| 77 | | - var elems = {}; |
| 78 | | - var elemClasses = [ |
| 79 | | - "rail", "mergeoffset", "node", "arrow u", "arrow u sm", "line", |
| 80 | | - "arrow merge r", "line merge", "arrow warp", "line warp" |
| 81 | | - ]; |
| 82 | | - for( var i=0; i<elemClasses.length; i++ ){ |
| 83 | | - var cls = elemClasses[i]; |
| 84 | | - var elem = document.createElement("div"); |
| 85 | | - elem.className = "tl-" + cls; |
| 86 | | - if( cls.indexOf("line")==0 ) elem.className += " v"; |
| 87 | | - canvasDiv.appendChild(elem); |
| 88 | | - var k = cls.replace(/\s/g, "_"); |
| 89 | | - var r = elem.getBoundingClientRect(); |
| 90 | | - var w = Math.round(r.right - r.left); |
| 91 | | - var h = Math.round(r.bottom - r.top); |
| 92 | | - elems[k] = {w: w, h: h, cls: cls}; |
| 93 | | - } |
| 94 | | - node = elems.node; |
| 95 | | - arrow = elems.arrow_u; |
| 96 | | - arrowSmall = elems.arrow_u_sm; |
| 97 | | - line = elems.line; |
| 98 | | - mArrow = elems.arrow_merge_r; |
| 99 | | - mLine = elems.line_merge; |
| 100 | | - wArrow = elems.arrow_warp; |
| 101 | | - wLine = elems.line_warp; |
| 102 | | - |
| 103 | | - var minRailPitch = Math.ceil((node.w+line.w)/2 + mArrow.w + 1); |
| 104 | | - if( tx.iRailPitch>0 ){ |
| 105 | | - railPitch = tx.iRailPitch; |
| 106 | | - }else{ |
| 107 | | - railPitch = elems.rail.w; |
| 108 | | - railPitch -= Math.floor((tx.nrail-1)*(railPitch-minRailPitch)/21); |
| 109 | | - } |
| 110 | | - railPitch = Math.max(railPitch, minRailPitch); |
| 111 | | - |
| 112 | | - if( tx.nomo ){ |
| 113 | | - mergeOffset = 0; |
| 114 | | - }else{ |
| 115 | | - mergeOffset = railPitch-minRailPitch-mLine.w; |
| 116 | | - mergeOffset = Math.min(mergeOffset, elems.mergeoffset.w); |
| 117 | | - mergeOffset = mergeOffset>0 ? mergeOffset + line.w/2 : 0; |
| 118 | | - } |
| 119 | | - |
| 120 | | - var canvasWidth = (tx.nrail-1)*railPitch + node.w; |
| 121 | | - canvasDiv.style.width = canvasWidth + "px"; |
| 122 | | - canvasDiv.style.position = "relative"; |
| 123 | | -} |
| 124 | | -function drawBox(cls,color,x0,y0,x1,y1){ |
| 125 | | - var n = document.createElement("div"); |
| 126 | | - x0 = Math.floor(x0); |
| 127 | | - y0 = Math.floor(y0); |
| 128 | | - x1 = x1 || x1===0 ? Math.floor(x1) : x0; |
| 129 | | - y1 = y1 || y1===0 ? Math.floor(y1) : y0; |
| 130 | | - if( x0>x1 ){ var t=x0; x0=x1; x1=t; } |
| 131 | | - if( y0>y1 ){ var t=y0; y0=y1; y1=t; } |
| 132 | | - var w = x1-x0; |
| 133 | | - var h = y1-y0; |
| 134 | | - n.style.position = "absolute"; |
| 135 | | - n.style.left = x0+"px"; |
| 136 | | - n.style.top = y0+"px"; |
| 137 | | - if( w ) n.style.width = w+"px"; |
| 138 | | - if( h ) n.style.height = h+"px"; |
| 139 | | - if( color ) n.style.backgroundColor = color; |
| 140 | | - n.className = "tl-"+cls; |
| 141 | | - canvasDiv.appendChild(n); |
| 142 | | - return n; |
| 143 | | -} |
| 144 | | -function absoluteY(obj){ |
| 145 | | - var top = 0; |
| 146 | | - if( obj.offsetParent ){ |
| 147 | | - do{ |
| 148 | | - top += obj.offsetTop; |
| 149 | | - }while( obj = obj.offsetParent ); |
| 150 | | - } |
| 151 | | - return top; |
| 152 | | -} |
| 153 | | -function miLineY(p){ |
| 154 | | - return p.y + node.h - mLine.w - 1; |
| 155 | | -} |
| 156 | | -function drawLine(elem,color,x0,y0,x1,y1){ |
| 157 | | - var cls = elem.cls + " "; |
| 158 | | - if( x1===null ){ |
| 159 | | - x1 = x0+elem.w; |
| 160 | | - cls += "v"; |
| 161 | | - }else{ |
| 162 | | - y1 = y0+elem.w; |
| 163 | | - cls += "h"; |
| 164 | | - } |
| 165 | | - drawBox(cls,color,x0,y0,x1,y1); |
| 166 | | -} |
| 167 | | -function drawUpArrow(from,to,color){ |
| 168 | | - var y = to.y + node.h; |
| 169 | | - var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0); |
| 170 | | - var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow; |
| 171 | | - var x = to.x + (node.w-line.w)/2; |
| 172 | | - var y0 = from.y + node.h/2; |
| 173 | | - var y1 = Math.ceil(to.y + node.h + arw.h/2); |
| 174 | | - drawLine(line,color,x,y0,null,y1); |
| 175 | | - x = to.x + (node.w-arw.w)/2; |
| 176 | | - var n = drawBox(arw.cls,null,x,y); |
| 177 | | - n.style.borderBottomColor = color; |
| 178 | | -} |
| 179 | | -function drawMergeLine(x0,y0,x1,y1){ |
| 180 | | - drawLine(mLine,null,x0,y0,x1,y1); |
| 181 | | -} |
| 182 | | -function drawMergeArrow(p,rail){ |
| 183 | | - var x0 = rail*railPitch + node.w/2; |
| 184 | | - if( rail in mergeLines ){ |
| 185 | | - x0 += mergeLines[rail]; |
| 186 | | - if( p.r<rail ) x0 += mLine.w; |
| 187 | | - }else{ |
| 188 | | - x0 += (p.r<rail ? -1 : 1)*line.w/2; |
| 189 | | - } |
| 190 | | - var x1 = mArrow.w ? mArrow.w/2 : -node.w/2; |
| 191 | | - x1 = p.x + (p.r<rail ? node.w + Math.ceil(x1) : -x1); |
| 192 | | - var y = miLineY(p); |
| 193 | | - drawMergeLine(x0,y,x1,null); |
| 194 | | - var x = p.x + (p.r<rail ? node.w : -mArrow.w); |
| 195 | | - var cls = "arrow merge " + (p.r<rail ? "l" : "r"); |
| 196 | | - drawBox(cls,null,x,y+(mLine.w-mArrow.h)/2); |
| 197 | | -} |
| 198 | | -function drawNode(p, btm){ |
| 199 | | - if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg); |
| 200 | | - var cls = node.cls; |
| 201 | | - if( p.mi.length ) cls += " merge"; |
| 202 | | - if( p.f&1 ) cls += " leaf"; |
| 203 | | - var n = drawBox(cls,p.bg,p.x,p.y); |
| 204 | | - n.id = "tln"+p.id; |
| 205 | | - n.onclick = clickOnNode; |
| 206 | | - n.style.zIndex = 10; |
| 207 | | - if( !tx.omitDescenders ){ |
| 208 | | - if( p.u==0 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg); |
| 209 | | - if( p.d ) drawUpArrow({x: p.x, y: btm-node.h/2},p,p.fg); |
| 210 | | - } |
| 211 | | - if( p.mo>=0 ){ |
| 212 | | - var x0 = p.x + node.w/2; |
| 213 | | - var x1 = p.mo*railPitch + node.w/2; |
| 214 | | - var u = tx.rowinfo[p.mu-tx.iTopRow]; |
| 215 | | - var y1 = miLineY(u); |
| 216 | | - if( p.u<0 || p.mo!=p.r ){ |
| 217 | | - x1 += mergeLines[p.mo] = -mLine.w/2; |
| 218 | | - var y0 = p.y+2; |
| 219 | | - if( p.r!=p.mo ) drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null); |
| 220 | | - drawMergeLine(x1,y0+mLine.w,null,y1); |
| 221 | | - }else if( mergeOffset ){ |
| 222 | | - mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset; |
| 223 | | - x1 += mergeLines[p.mo]; |
| 224 | | - drawMergeLine(x1,p.y+node.h/2,null,y1); |
| 225 | | - }else{ |
| 226 | | - delete mergeLines[p.mo]; |
| 227 | | - } |
| 228 | | - } |
| 229 | | - for( var i=0; i<p.au.length; i+=2 ){ |
| 230 | | - var rail = p.au[i]; |
| 231 | | - var x0 = p.x + node.w/2; |
| 232 | | - var x1 = rail*railPitch + (node.w-line.w)/2; |
| 233 | | - if( x0<x1 ){ |
| 234 | | - x0 = Math.ceil(x0); |
| 235 | | - x1 += line.w; |
| 236 | | - } |
| 237 | | - var y0 = p.y + (node.h-line.w)/2; |
| 238 | | - var u = tx.rowinfo[p.au[i+1]-tx.iTopRow]; |
| 239 | | - if( u.id<p.id ){ |
| 240 | | - drawLine(line,u.fg,x0,y0,x1,null); |
| 241 | | - drawUpArrow(p,u,u.fg); |
| 242 | | - }else{ |
| 243 | | - var y1 = u.y + (node.h-line.w)/2; |
| 244 | | - drawLine(wLine,u.fg,x0,y0,x1,null); |
| 245 | | - drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w); |
| 246 | | - drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null); |
| 247 | | - var x = u.x-wArrow.w; |
| 248 | | - var y = u.y+(node.h-wArrow.h)/2; |
| 249 | | - var n = drawBox(wArrow.cls,null,x,y); |
| 250 | | - if( u.fg ) n.style.borderLeftColor = u.fg; |
| 251 | | - } |
| 252 | | - } |
| 253 | | - for( var i=0; i<p.mi.length; i++ ){ |
| 254 | | - var rail = p.mi[i]; |
| 255 | | - if( rail<0 ){ |
| 256 | | - rail = -rail; |
| 257 | | - mergeLines[rail] = -mLine.w/2; |
| 258 | | - var x = rail*railPitch + (node.w-mLine.w)/2; |
| 259 | | - drawMergeLine(x,miLineY(p),null,btm); |
| 260 | | - } |
| 261 | | - drawMergeArrow(p,rail); |
| 262 | | - } |
| 263 | | -} |
| 264 | | -var mergeLines; |
| 265 | | -function renderGraph(){ |
| 266 | | - mergeLines = {}; |
| 267 | | - canvasDiv.innerHTML = ""; |
| 268 | | - var canvasY = absoluteY(canvasDiv); |
| 269 | | - for(var i=0; i<tx.rowinfo.length; i++ ){ |
| 270 | | - var e = document.getElementById("m"+tx.rowinfo[i].id); |
| 271 | | - tx.rowinfo[i].y = absoluteY(e) - canvasY; |
| 272 | | - tx.rowinfo[i].x = tx.rowinfo[i].r*railPitch; |
| 273 | | - } |
| 274 | | - var tlBtm = document.querySelector(".timelineBottom"); |
| 275 | | - if( tlBtm.offsetHeight<node.h ){ |
| 276 | | - tlBtm.style.height = node.h + "px"; |
| 277 | | - } |
| 278 | | - var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight; |
| 279 | | - for( var i=tx.rowinfo.length-1; i>=0; i-- ){ |
| 280 | | - drawNode(tx.rowinfo[i], btm); |
| 281 | | - } |
| 282 | | -} |
| 283 | | -var selRow; |
| 284 | | -function clickOnNode(){ |
| 285 | | - var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow]; |
| 286 | | - if( !selRow ){ |
| 287 | | - selRow = p; |
| 288 | | - this.className += " sel"; |
| 289 | | - canvasDiv.className += " sel"; |
| 290 | | - }else if( selRow==p ){ |
| 291 | | - selRow = null; |
| 292 | | - this.className = this.className.replace(" sel", ""); |
| 293 | | - canvasDiv.className = canvasDiv.className.replace(" sel", ""); |
| 294 | | - }else{ |
| 295 | | - if( tx.fileDiff ){ |
| 296 | | - location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h |
| 297 | | - }else{ |
| 298 | | - location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h |
| 299 | | - } |
| 300 | | - } |
| 301 | | -} |
| 302 | | -function changeDisplay(selector,value){ |
| 303 | | - var x = document.getElementsByClassName(selector); |
| 304 | | - var n = x.length; |
| 305 | | - for(var i=0; i<n; i++) {x[i].style.display = value;} |
| 306 | | -} |
| 307 | | -function changeDisplayById(id,value){ |
| 308 | | - var x = document.getElementById(id); |
| 309 | | - if(x) x.style.display=value; |
| 310 | | -} |
| 311 | | -function toggleDetail(){ |
| 312 | | - var id = parseInt(this.getAttribute('data-id')) |
| 313 | | - var x = gebi("detail-"+id); |
| 314 | | - if( x.style.display=="inline" ){ |
| 315 | | - x.style.display="none"; |
| 316 | | - changeDisplayById("ellipsis-"+id,"inline"); |
| 317 | | - changeDisplayById("links-"+id,"none"); |
| 318 | | - }else{ |
| 319 | | - x.style.display="inline"; |
| 320 | | - changeDisplayById("ellipsis-"+id,"none"); |
| 321 | | - changeDisplayById("links-"+id,"inline"); |
| 322 | | - } |
| 323 | | - checkHeight(); |
| 324 | | -} |
| 325 | | -function scrollToSelected(){ |
| 326 | | - var x = document.getElementsByClassName('timelineSelected'); |
| 327 | | - if(x[0]){ |
| 328 | | - var h = window.innerHeight; |
| 329 | | - var y = absoluteY(x[0]) - h/2; |
| 330 | | - if( y>0 ) window.scrollTo(0, y); |
| 331 | | - } |
| 332 | | -} |
| 333 | | -var lastRow = gebi("m"+tx.rowinfo[tx.rowinfo.length-1].id); |
| 334 | | -var lastY = 0; |
| 335 | | -function checkHeight(){ |
| 336 | | - var h = absoluteY(lastRow); |
| 337 | | - if( h!=lastY ){ |
| 338 | | - renderGraph(); |
| 339 | | - lastY = h; |
| 340 | | - } |
| 341 | | - setTimeout(checkHeight, 1000); |
| 342 | | -} |
| 343 | | -initGraph(); |
| 344 | | -checkHeight(); |
| 345 | | -scrollToSelected(); |
| 346 | | - |
| 347 | | -/* Set the onclick= attributes for elements of the "Compact" display |
| 348 | | -** mode so that clicking turns the details on and off. */ |
| 349 | | -(function(){ |
| 350 | | - var lx = document.getElementsByClassName('timelineEllipsis'); |
| 351 | | - var i; |
| 352 | | - for(i=0; i<lx.length; i++){ |
| 353 | | - if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 354 | | - } |
| 355 | | - var lx = document.getElementsByClassName('timelineCompactComment'); |
| 356 | | - for(i=0; i<lx.length; i++){ |
| 357 | | - if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 51 | +var amendCssOnce = 1; // Only change the CSS one time |
| 52 | +function amendCss(circleNodes,showArrowheads){ |
| 53 | + if( !amendCssOnce ) return; |
| 54 | + var css = ""; |
| 55 | + if( circleNodes ){ |
| 56 | + css += ".tl-node, .tl-node:after { border-radius: 50%; }"; |
| 57 | + } |
| 58 | + if( showArrowheads ){ |
| 59 | + css += ".tl-arrow.u { display: none; }"; |
| 60 | + } |
| 61 | + if( css!=="" ){ |
| 62 | + var style = document.createElement("style"); |
| 63 | + style.textContent = css; |
| 64 | + document.querySelector("head").appendChild(style); |
| 65 | + } |
| 66 | + amendCssOnce = 0; |
| 67 | +} |
| 68 | + |
| 69 | +function TimelineGraph(tx){ |
| 70 | + var topObj = document.getElementById("timelineTable"+tx.iTableId); |
| 71 | + amendCss(tx.circleNodes, tx.showArrowheads); |
| 72 | + var canvasDiv; |
| 73 | + var railPitch; |
| 74 | + var mergeOffset; |
| 75 | + var node, arrow, arrowSmall, line, mArrow, mLine, wArrow, wLine; |
| 76 | + function initGraph(){ |
| 77 | + var parent = topObj.rows[0].cells[1]; |
| 78 | + parent.style.verticalAlign = "top"; |
| 79 | + canvasDiv = document.createElement("div"); |
| 80 | + canvasDiv.className = "tl-canvas"; |
| 81 | + canvasDiv.style.position = "absolute"; |
| 82 | + parent.appendChild(canvasDiv); |
| 83 | + |
| 84 | + var elems = {}; |
| 85 | + var elemClasses = [ |
| 86 | + "rail", "mergeoffset", "node", "arrow u", "arrow u sm", "line", |
| 87 | + "arrow merge r", "line merge", "arrow warp", "line warp" |
| 88 | + ]; |
| 89 | + for( var i=0; i<elemClasses.length; i++ ){ |
| 90 | + var cls = elemClasses[i]; |
| 91 | + var elem = document.createElement("div"); |
| 92 | + elem.className = "tl-" + cls; |
| 93 | + if( cls.indexOf("line")==0 ) elem.className += " v"; |
| 94 | + canvasDiv.appendChild(elem); |
| 95 | + var k = cls.replace(/\s/g, "_"); |
| 96 | + var r = elem.getBoundingClientRect(); |
| 97 | + var w = Math.round(r.right - r.left); |
| 98 | + var h = Math.round(r.bottom - r.top); |
| 99 | + elems[k] = {w: w, h: h, cls: cls}; |
| 100 | + } |
| 101 | + node = elems.node; |
| 102 | + arrow = elems.arrow_u; |
| 103 | + arrowSmall = elems.arrow_u_sm; |
| 104 | + line = elems.line; |
| 105 | + mArrow = elems.arrow_merge_r; |
| 106 | + mLine = elems.line_merge; |
| 107 | + wArrow = elems.arrow_warp; |
| 108 | + wLine = elems.line_warp; |
| 109 | + |
| 110 | + var minRailPitch = Math.ceil((node.w+line.w)/2 + mArrow.w + 1); |
| 111 | + if( tx.iRailPitch>0 ){ |
| 112 | + railPitch = tx.iRailPitch; |
| 113 | + }else{ |
| 114 | + railPitch = elems.rail.w; |
| 115 | + railPitch -= Math.floor((tx.nrail-1)*(railPitch-minRailPitch)/21); |
| 116 | + } |
| 117 | + railPitch = Math.max(railPitch, minRailPitch); |
| 118 | + |
| 119 | + if( tx.nomo ){ |
| 120 | + mergeOffset = 0; |
| 121 | + }else{ |
| 122 | + mergeOffset = railPitch-minRailPitch-mLine.w; |
| 123 | + mergeOffset = Math.min(mergeOffset, elems.mergeoffset.w); |
| 124 | + mergeOffset = mergeOffset>0 ? mergeOffset + line.w/2 : 0; |
| 125 | + } |
| 126 | + |
| 127 | + var canvasWidth = (tx.nrail-1)*railPitch + node.w; |
| 128 | + canvasDiv.style.width = canvasWidth + "px"; |
| 129 | + canvasDiv.style.position = "relative"; |
| 130 | + } |
| 131 | + function drawBox(cls,color,x0,y0,x1,y1){ |
| 132 | + var n = document.createElement("div"); |
| 133 | + x0 = Math.floor(x0); |
| 134 | + y0 = Math.floor(y0); |
| 135 | + x1 = x1 || x1===0 ? Math.floor(x1) : x0; |
| 136 | + y1 = y1 || y1===0 ? Math.floor(y1) : y0; |
| 137 | + if( x0>x1 ){ var t=x0; x0=x1; x1=t; } |
| 138 | + if( y0>y1 ){ var t=y0; y0=y1; y1=t; } |
| 139 | + var w = x1-x0; |
| 140 | + var h = y1-y0; |
| 141 | + n.style.position = "absolute"; |
| 142 | + n.style.left = x0+"px"; |
| 143 | + n.style.top = y0+"px"; |
| 144 | + if( w ) n.style.width = w+"px"; |
| 145 | + if( h ) n.style.height = h+"px"; |
| 146 | + if( color ) n.style.backgroundColor = color; |
| 147 | + n.className = "tl-"+cls; |
| 148 | + canvasDiv.appendChild(n); |
| 149 | + return n; |
| 150 | + } |
| 151 | + function absoluteY(obj){ |
| 152 | + var top = 0; |
| 153 | + if( obj.offsetParent ){ |
| 154 | + do{ |
| 155 | + top += obj.offsetTop; |
| 156 | + }while( obj = obj.offsetParent ); |
| 157 | + } |
| 158 | + return top; |
| 159 | + } |
| 160 | + function miLineY(p){ |
| 161 | + return p.y + node.h - mLine.w - 1; |
| 162 | + } |
| 163 | + function drawLine(elem,color,x0,y0,x1,y1){ |
| 164 | + var cls = elem.cls + " "; |
| 165 | + if( x1===null ){ |
| 166 | + x1 = x0+elem.w; |
| 167 | + cls += "v"; |
| 168 | + }else{ |
| 169 | + y1 = y0+elem.w; |
| 170 | + cls += "h"; |
| 171 | + } |
| 172 | + drawBox(cls,color,x0,y0,x1,y1); |
| 173 | + } |
| 174 | + function drawUpArrow(from,to,color){ |
| 175 | + var y = to.y + node.h; |
| 176 | + var arrowSpace = from.y - y + (!from.id || from.r!=to.r ? node.h/2 : 0); |
| 177 | + var arw = arrowSpace < arrow.h*1.5 ? arrowSmall : arrow; |
| 178 | + var x = to.x + (node.w-line.w)/2; |
| 179 | + var y0 = from.y + node.h/2; |
| 180 | + var y1 = Math.ceil(to.y + node.h + arw.h/2); |
| 181 | + drawLine(line,color,x,y0,null,y1); |
| 182 | + x = to.x + (node.w-arw.w)/2; |
| 183 | + var n = drawBox(arw.cls,null,x,y); |
| 184 | + n.style.borderBottomColor = color; |
| 185 | + } |
| 186 | + function drawMergeLine(x0,y0,x1,y1){ |
| 187 | + drawLine(mLine,null,x0,y0,x1,y1); |
| 188 | + } |
| 189 | + function drawMergeArrow(p,rail){ |
| 190 | + var x0 = rail*railPitch + node.w/2; |
| 191 | + if( rail in mergeLines ){ |
| 192 | + x0 += mergeLines[rail]; |
| 193 | + if( p.r<rail ) x0 += mLine.w; |
| 194 | + }else{ |
| 195 | + x0 += (p.r<rail ? -1 : 1)*line.w/2; |
| 196 | + } |
| 197 | + var x1 = mArrow.w ? mArrow.w/2 : -node.w/2; |
| 198 | + x1 = p.x + (p.r<rail ? node.w + Math.ceil(x1) : -x1); |
| 199 | + var y = miLineY(p); |
| 200 | + drawMergeLine(x0,y,x1,null); |
| 201 | + var x = p.x + (p.r<rail ? node.w : -mArrow.w); |
| 202 | + var cls = "arrow merge " + (p.r<rail ? "l" : "r"); |
| 203 | + drawBox(cls,null,x,y+(mLine.w-mArrow.h)/2); |
| 204 | + } |
| 205 | + function drawNode(p, btm){ |
| 206 | + if( p.u>0 ) drawUpArrow(p,tx.rowinfo[p.u-tx.iTopRow],p.fg); |
| 207 | + var cls = node.cls; |
| 208 | + if( p.mi.length ) cls += " merge"; |
| 209 | + if( p.f&1 ) cls += " leaf"; |
| 210 | + var n = drawBox(cls,p.bg,p.x,p.y); |
| 211 | + n.id = "tln"+p.id; |
| 212 | + n.onclick = clickOnNode; |
| 213 | + n.style.zIndex = 10; |
| 214 | + if( !tx.omitDescenders ){ |
| 215 | + if( p.u==0 ) drawUpArrow(p,{x: p.x, y: -node.h},p.fg); |
| 216 | + if( p.d ) drawUpArrow({x: p.x, y: btm-node.h/2},p,p.fg); |
| 217 | + } |
| 218 | + if( p.mo>=0 ){ |
| 219 | + var x0 = p.x + node.w/2; |
| 220 | + var x1 = p.mo*railPitch + node.w/2; |
| 221 | + var u = tx.rowinfo[p.mu-tx.iTopRow]; |
| 222 | + var y1 = miLineY(u); |
| 223 | + if( p.u<0 || p.mo!=p.r ){ |
| 224 | + x1 += mergeLines[p.mo] = -mLine.w/2; |
| 225 | + var y0 = p.y+2; |
| 226 | + if( p.r!=p.mo ) drawMergeLine(x0,y0,x1+(x0<x1 ? mLine.w : 0),null); |
| 227 | + drawMergeLine(x1,y0+mLine.w,null,y1); |
| 228 | + }else if( mergeOffset ){ |
| 229 | + mergeLines[p.mo] = u.r<p.r ? -mergeOffset-mLine.w : mergeOffset; |
| 230 | + x1 += mergeLines[p.mo]; |
| 231 | + drawMergeLine(x1,p.y+node.h/2,null,y1); |
| 232 | + }else{ |
| 233 | + delete mergeLines[p.mo]; |
| 234 | + } |
| 235 | + } |
| 236 | + for( var i=0; i<p.au.length; i+=2 ){ |
| 237 | + var rail = p.au[i]; |
| 238 | + var x0 = p.x + node.w/2; |
| 239 | + var x1 = rail*railPitch + (node.w-line.w)/2; |
| 240 | + if( x0<x1 ){ |
| 241 | + x0 = Math.ceil(x0); |
| 242 | + x1 += line.w; |
| 243 | + } |
| 244 | + var y0 = p.y + (node.h-line.w)/2; |
| 245 | + var u = tx.rowinfo[p.au[i+1]-tx.iTopRow]; |
| 246 | + if( u.id<p.id ){ |
| 247 | + drawLine(line,u.fg,x0,y0,x1,null); |
| 248 | + drawUpArrow(p,u,u.fg); |
| 249 | + }else{ |
| 250 | + var y1 = u.y + (node.h-line.w)/2; |
| 251 | + drawLine(wLine,u.fg,x0,y0,x1,null); |
| 252 | + drawLine(wLine,u.fg,x1-line.w,y0,null,y1+line.w); |
| 253 | + drawLine(wLine,u.fg,x1,y1,u.x-wArrow.w/2,null); |
| 254 | + var x = u.x-wArrow.w; |
| 255 | + var y = u.y+(node.h-wArrow.h)/2; |
| 256 | + var n = drawBox(wArrow.cls,null,x,y); |
| 257 | + if( u.fg ) n.style.borderLeftColor = u.fg; |
| 258 | + } |
| 259 | + } |
| 260 | + for( var i=0; i<p.mi.length; i++ ){ |
| 261 | + var rail = p.mi[i]; |
| 262 | + if( rail<0 ){ |
| 263 | + rail = -rail; |
| 264 | + mergeLines[rail] = -mLine.w/2; |
| 265 | + var x = rail*railPitch + (node.w-mLine.w)/2; |
| 266 | + drawMergeLine(x,miLineY(p),null,btm); |
| 267 | + } |
| 268 | + drawMergeArrow(p,rail); |
| 269 | + } |
| 270 | + } |
| 271 | + var mergeLines; |
| 272 | + function renderGraph(){ |
| 273 | + mergeLines = {}; |
| 274 | + canvasDiv.innerHTML = ""; |
| 275 | + var canvasY = absoluteY(canvasDiv); |
| 276 | + for(var i=0; i<tx.rowinfo.length; i++ ){ |
| 277 | + var e = document.getElementById("m"+tx.rowinfo[i].id); |
| 278 | + tx.rowinfo[i].y = absoluteY(e) - canvasY; |
| 279 | + tx.rowinfo[i].x = tx.rowinfo[i].r*railPitch; |
| 280 | + } |
| 281 | + var tlBtm = document.querySelector(".timelineBottom"); |
| 282 | + if( tlBtm.offsetHeight<node.h ){ |
| 283 | + tlBtm.style.height = node.h + "px"; |
| 284 | + } |
| 285 | + var btm = absoluteY(tlBtm) - canvasY + tlBtm.offsetHeight; |
| 286 | + for( var i=tx.rowinfo.length-1; i>=0; i-- ){ |
| 287 | + drawNode(tx.rowinfo[i], btm); |
| 288 | + } |
| 289 | + } |
| 290 | + var selRow; |
| 291 | + function clickOnNode(){ |
| 292 | + var p = tx.rowinfo[parseInt(this.id.match(/\d+$/)[0], 10)-tx.iTopRow]; |
| 293 | + if( !selRow ){ |
| 294 | + selRow = p; |
| 295 | + this.className += " sel"; |
| 296 | + canvasDiv.className += " sel"; |
| 297 | + }else if( selRow==p ){ |
| 298 | + selRow = null; |
| 299 | + this.className = this.className.replace(" sel", ""); |
| 300 | + canvasDiv.className = canvasDiv.className.replace(" sel", ""); |
| 301 | + }else{ |
| 302 | + if( tx.fileDiff ){ |
| 303 | + location.href=tx.baseUrl + "/fdiff?v1="+selRow.h+"&v2="+p.h |
| 304 | + }else{ |
| 305 | + location.href=tx.baseUrl + "/vdiff?from="+selRow.h+"&to="+p.h |
| 306 | + } |
| 307 | + } |
| 308 | + } |
| 309 | + function changeDisplay(selector,value){ |
| 310 | + var x = document.getElementsByClassName(selector); |
| 311 | + var n = x.length; |
| 312 | + for(var i=0; i<n; i++) {x[i].style.display = value;} |
| 313 | + } |
| 314 | + function changeDisplayById(id,value){ |
| 315 | + var x = document.getElementById(id); |
| 316 | + if(x) x.style.display=value; |
| 317 | + } |
| 318 | + function toggleDetail(){ |
| 319 | + var id = parseInt(this.getAttribute('data-id')) |
| 320 | + var x = gebi("detail-"+id); |
| 321 | + if( x.style.display=="inline" ){ |
| 322 | + x.style.display="none"; |
| 323 | + changeDisplayById("ellipsis-"+id,"inline"); |
| 324 | + changeDisplayById("links-"+id,"none"); |
| 325 | + }else{ |
| 326 | + x.style.display="inline"; |
| 327 | + changeDisplayById("ellipsis-"+id,"none"); |
| 328 | + changeDisplayById("links-"+id,"inline"); |
| 329 | + } |
| 330 | + checkHeight(); |
| 331 | + } |
| 332 | + function scrollToSelected(){ |
| 333 | + var x = document.getElementsByClassName('timelineSelected'); |
| 334 | + if(x[0]){ |
| 335 | + var h = window.innerHeight; |
| 336 | + var y = absoluteY(x[0]) - h/2; |
| 337 | + if( y>0 ) window.scrollTo(0, y); |
| 338 | + } |
| 339 | + } |
| 340 | + var lastRow = gebi("m"+tx.rowinfo[tx.rowinfo.length-1].id); |
| 341 | + var lastY = 0; |
| 342 | + function checkHeight(){ |
| 343 | + var h = absoluteY(lastRow); |
| 344 | + if( h!=lastY ){ |
| 345 | + renderGraph(); |
| 346 | + lastY = h; |
| 347 | + } |
| 348 | + setTimeout(checkHeight, 1000); |
| 349 | + } |
| 350 | + initGraph(); |
| 351 | + checkHeight(); |
| 352 | + scrollToSelected(); |
| 353 | + |
| 354 | + /* Set the onclick= attributes for elements of the "Compact" display |
| 355 | + ** mode so that clicking turns the details on and off. |
| 356 | + */ |
| 357 | + var lx = topObj.getElementsByClassName('timelineEllipsis'); |
| 358 | + var i; |
| 359 | + for(i=0; i<lx.length; i++){ |
| 360 | + if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 361 | + } |
| 362 | + lx = topObj.getElementsByClassName('timelineCompactComment'); |
| 363 | + for(i=0; i<lx.length; i++){ |
| 364 | + if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail; |
| 365 | + } |
| 366 | +} |
| 367 | + |
| 368 | +/* Look for all timeline-data-NN objects. Load each one and draw |
| 369 | +** a graph for each one. |
| 370 | +*/ |
| 371 | +(function(){ |
| 372 | + var i; |
| 373 | + for(i=0; 1; i++){ |
| 374 | + var dataObj = document.getElementById("timeline-data-"+i); |
| 375 | + if(!dataObj) break; |
| 376 | + var txJson = dataObj.textContent || dataObj.innerText; |
| 377 | + var tx = JSON.parse(txJson); |
| 378 | + TimelineGraph(tx); |
| 358 | 379 | } |
| 359 | 380 | }()) |
| 360 | 381 | |