Fossil SCM

fossil-scm / src / graph.js
Source Blame History 824 lines
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, "&amp;")
6908832… drh 672 .replace(/</g, "&lt;")
6908832… drh 673 .replace(/>/g, "&gt;")
6908832… drh 674 .replace(/"/g, "&quot;")
6908832… drh 675 .replace(/'/g, "&#039;");
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 }());

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button