Fossil SCM

Fix the timeline graph generator so that it works with two or more graphs on a single page.

drh 2017-12-06 17:38 trunk
Commit 68635d60dff02af1ea9a9351121962cd17dd691ec7797cce180b49634eaa3e7d
+3 -2
--- src/finfo.c
+++ src/finfo.c
@@ -312,10 +312,11 @@
312312
int brBg = P("brbg")!=0;
313313
int uBg = P("ubg")!=0;
314314
int fDebug = atoi(PD("debug","0"));
315315
int fShowId = P("showid")!=0;
316316
Stmt qparent;
317
+ int iTableId = timeline_tableid();
317318
int tmFlags = 0; /* Viewing mode */
318319
const char *zStyle; /* Viewing mode name */
319320
320321
login_check_credentials();
321322
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -446,11 +447,11 @@
446447
if( fShowId ) blob_appendf(&title, " (%d)", fnid);
447448
}
448449
@ <h2>%b(&title)</h2>
449450
blob_reset(&title);
450451
pGraph = graph_init();
451
- @ <table id="timelineTable" class="timelineTable">
452
+ @ <table id="timelineTable%d(iTableId)" class="timelineTable">
452453
if( baseCheckin ){
453454
db_prepare(&qparent,
454455
"SELECT DISTINCT pid FROM mlink"
455456
" WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
456457
" AND pmid IN (SELECT rid FROM ancestor)"
@@ -635,11 +636,11 @@
635636
}else{
636637
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
637638
}
638639
}
639640
@ </table>
640
- timeline_output_graph_javascript(pGraph, 0, 1);
641
+ timeline_output_graph_javascript(pGraph, 0, iTableId, 1);
641642
style_footer();
642643
}
643644
644645
/*
645646
** WEBPAGE: mlink
646647
--- src/finfo.c
+++ src/finfo.c
@@ -312,10 +312,11 @@
312 int brBg = P("brbg")!=0;
313 int uBg = P("ubg")!=0;
314 int fDebug = atoi(PD("debug","0"));
315 int fShowId = P("showid")!=0;
316 Stmt qparent;
 
317 int tmFlags = 0; /* Viewing mode */
318 const char *zStyle; /* Viewing mode name */
319
320 login_check_credentials();
321 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -446,11 +447,11 @@
446 if( fShowId ) blob_appendf(&title, " (%d)", fnid);
447 }
448 @ <h2>%b(&title)</h2>
449 blob_reset(&title);
450 pGraph = graph_init();
451 @ <table id="timelineTable" class="timelineTable">
452 if( baseCheckin ){
453 db_prepare(&qparent,
454 "SELECT DISTINCT pid FROM mlink"
455 " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
456 " AND pmid IN (SELECT rid FROM ancestor)"
@@ -635,11 +636,11 @@
635 }else{
636 @ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
637 }
638 }
639 @ </table>
640 timeline_output_graph_javascript(pGraph, 0, 1);
641 style_footer();
642 }
643
644 /*
645 ** WEBPAGE: mlink
646
--- src/finfo.c
+++ src/finfo.c
@@ -312,10 +312,11 @@
312 int brBg = P("brbg")!=0;
313 int uBg = P("ubg")!=0;
314 int fDebug = atoi(PD("debug","0"));
315 int fShowId = P("showid")!=0;
316 Stmt qparent;
317 int iTableId = timeline_tableid();
318 int tmFlags = 0; /* Viewing mode */
319 const char *zStyle; /* Viewing mode name */
320
321 login_check_credentials();
322 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -446,11 +447,11 @@
447 if( fShowId ) blob_appendf(&title, " (%d)", fnid);
448 }
449 @ <h2>%b(&title)</h2>
450 blob_reset(&title);
451 pGraph = graph_init();
452 @ <table id="timelineTable%d(iTableId)" class="timelineTable">
453 if( baseCheckin ){
454 db_prepare(&qparent,
455 "SELECT DISTINCT pid FROM mlink"
456 " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
457 " AND pmid IN (SELECT rid FROM ancestor)"
@@ -635,11 +636,11 @@
636 }else{
637 @ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
638 }
639 }
640 @ </table>
641 timeline_output_graph_javascript(pGraph, 0, iTableId, 1);
642 style_footer();
643 }
644
645 /*
646 ** WEBPAGE: mlink
647
+332 -311
--- src/graph.js
+++ src/graph.js
@@ -1,12 +1,13 @@
11
/* This module contains javascript needed to render timeline graphs in Fossil.
22
**
33
** 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:
66
**
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
89
** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit
910
** "iRailPitch": INTEGER, // Spacing between vertical lines (px)
1011
** "colorGraph": BOOLEAN, // True to put color on graph lines
1112
** "nomo": BOOLEAN, // True to join merge lines with rails
1213
** "iTopRow": INTEGER, // Index of top-most row in the graph
@@ -45,315 +46,335 @@
4546
** negative, then the rail position is the absolute value of mi[]
4647
** and a thin merge-arrow descender is drawn to the bottom of
4748
** the screen.
4849
** h: The artifact hash of the object being graphed
4950
*/
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);
358379
}
359380
}())
360381
--- src/graph.js
+++ src/graph.js
@@ -1,12 +1,13 @@
1 /* This module contains javascript needed to render timeline graphs in Fossil.
2 **
3 ** Prior to sourcing this script, there should be a separate
4 ** <script type='application/json' id='timeline-data'> containing JSON
5 ** like this:
6 **
7 ** { "circleNodes": BOOLEAN, // True for circle nodes. False for squares
 
8 ** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit
9 ** "iRailPitch": INTEGER, // Spacing between vertical lines (px)
10 ** "colorGraph": BOOLEAN, // True to put color on graph lines
11 ** "nomo": BOOLEAN, // True to join merge lines with rails
12 ** "iTopRow": INTEGER, // Index of top-most row in the graph
@@ -45,315 +46,335 @@
45 ** negative, then the rail position is the absolute value of mi[]
46 ** and a thin merge-arrow descender is drawn to the bottom of
47 ** the screen.
48 ** h: The artifact hash of the object being graphed
49 */
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;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358 }
359 }())
360
--- src/graph.js
+++ src/graph.js
@@ -1,12 +1,13 @@
1 /* This module contains javascript needed to render timeline graphs in Fossil.
2 **
3 ** Prior to sourcing this script, there should be a separate
4 ** <script type='application/json' id='timeline-data-NN'> for each graph,
5 ** each containing JSON like this:
6 **
7 ** { "iTableId": INTEGER, // Table sequence number (NN)
8 ** "circleNodes": BOOLEAN, // True for circle nodes. False for squares
9 ** "showArrowheads": BOOLEAN, // True for arrowheads. False to omit
10 ** "iRailPitch": INTEGER, // Spacing between vertical lines (px)
11 ** "colorGraph": BOOLEAN, // True to put color on graph lines
12 ** "nomo": BOOLEAN, // True to join merge lines with rails
13 ** "iTopRow": INTEGER, // Index of top-most row in the graph
@@ -45,315 +46,335 @@
46 ** negative, then the rail position is the absolute value of mi[]
47 ** and a thin merge-arrow descender is drawn to the bottom of
48 ** the screen.
49 ** h: The artifact hash of the object being graphed
50 */
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);
379 }
380 }())
381
+11
--- src/style.c
+++ src/style.c
@@ -85,10 +85,11 @@
8585
/*
8686
** Flags for various javascript files needed prior to </body>
8787
*/
8888
static int needHrefJs = 0; /* href.js */
8989
static int needSortJs = 0; /* sorttable.js */
90
+static int needGraphJs = 0; /* graph.js */
9091
9192
/*
9293
** Generate and return a anchor tag like this:
9394
**
9495
** <a href="URL">
@@ -504,10 +505,17 @@
504505
** Indicate that the table-sorting javascript is needed.
505506
*/
506507
void style_table_sorter(void){
507508
needSortJs = 1;
508509
}
510
+
511
+/*
512
+** Indicate that the table-sorting javascript is needed.
513
+*/
514
+void style_graph_generator(void){
515
+ needGraphJs = 1;
516
+}
509517
510518
/*
511519
** Generate code to load a single javascript file
512520
*/
513521
void style_load_one_js_file(const char *zFile){
@@ -529,10 +537,13 @@
529537
style_load_one_js_file("href.js");
530538
}
531539
if( needSortJs ){
532540
style_load_one_js_file("sorttable.js");
533541
}
542
+ if( needGraphJs ){
543
+ style_load_one_js_file("graph.js");
544
+ }
534545
}
535546
536547
/*
537548
** Draw the footer at the bottom of the page.
538549
*/
539550
--- src/style.c
+++ src/style.c
@@ -85,10 +85,11 @@
85 /*
86 ** Flags for various javascript files needed prior to </body>
87 */
88 static int needHrefJs = 0; /* href.js */
89 static int needSortJs = 0; /* sorttable.js */
 
90
91 /*
92 ** Generate and return a anchor tag like this:
93 **
94 ** <a href="URL">
@@ -504,10 +505,17 @@
504 ** Indicate that the table-sorting javascript is needed.
505 */
506 void style_table_sorter(void){
507 needSortJs = 1;
508 }
 
 
 
 
 
 
 
509
510 /*
511 ** Generate code to load a single javascript file
512 */
513 void style_load_one_js_file(const char *zFile){
@@ -529,10 +537,13 @@
529 style_load_one_js_file("href.js");
530 }
531 if( needSortJs ){
532 style_load_one_js_file("sorttable.js");
533 }
 
 
 
534 }
535
536 /*
537 ** Draw the footer at the bottom of the page.
538 */
539
--- src/style.c
+++ src/style.c
@@ -85,10 +85,11 @@
85 /*
86 ** Flags for various javascript files needed prior to </body>
87 */
88 static int needHrefJs = 0; /* href.js */
89 static int needSortJs = 0; /* sorttable.js */
90 static int needGraphJs = 0; /* graph.js */
91
92 /*
93 ** Generate and return a anchor tag like this:
94 **
95 ** <a href="URL">
@@ -504,10 +505,17 @@
505 ** Indicate that the table-sorting javascript is needed.
506 */
507 void style_table_sorter(void){
508 needSortJs = 1;
509 }
510
511 /*
512 ** Indicate that the table-sorting javascript is needed.
513 */
514 void style_graph_generator(void){
515 needGraphJs = 1;
516 }
517
518 /*
519 ** Generate code to load a single javascript file
520 */
521 void style_load_one_js_file(const char *zFile){
@@ -529,10 +537,13 @@
537 style_load_one_js_file("href.js");
538 }
539 if( needSortJs ){
540 style_load_one_js_file("sorttable.js");
541 }
542 if( needGraphJs ){
543 style_load_one_js_file("graph.js");
544 }
545 }
546
547 /*
548 ** Draw the footer at the bottom of the page.
549 */
550
+7 -4
--- src/timeline.c
+++ src/timeline.c
@@ -255,10 +255,11 @@
255255
int vid = 0; /* Current checkout version */
256256
int dateFormat = 0; /* 0: HH:MM (default) */
257257
int bCommentGitStyle = 0; /* Only show comments through first blank line */
258258
const char *zStyle; /* Sub-name for classes for the style */
259259
const char *zDateFmt;
260
+ int iTableId = timeline_tableid();
260261
261262
if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
262263
vid = db_lget_int("checkout", 0);
263264
}
264265
zPrevDate[0] = 0;
@@ -285,11 +286,11 @@
285286
db_static_prepare(&qbranch,
286287
"SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
287288
TAG_BRANCH
288289
);
289290
290
- @ <table id="timelineTable" class="timelineTable">
291
+ @ <table id="timelineTable%d(iTableId)" class="timelineTable">
291292
blob_zero(&comment);
292293
while( db_step(pQuery)==SQLITE_ROW ){
293294
int rid = db_column_int(pQuery, 0);
294295
const char *zUuid = db_column_text(pQuery, 1);
295296
int isLeaf = db_column_int(pQuery, 5);
@@ -721,11 +722,11 @@
721722
@ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
722723
}
723724
}
724725
@ </table>
725726
if( fchngQueryInit ) db_finalize(&fchngQuery);
726
- timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, 0);
727
+ timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, iTableId, 0);
727728
}
728729
729730
/*
730731
** Change the RGB background color given in the argument in a foreground
731732
** color with the same hue.
@@ -762,10 +763,11 @@
762763
** graph.
763764
*/
764765
void timeline_output_graph_javascript(
765766
GraphContext *pGraph, /* The graph to be displayed */
766767
int omitDescenders, /* True to omit descenders */
768
+ int iTableId, /* Which graph is this for */
767769
int fileDiff /* True for file diff. False for check-in diff */
768770
){
769771
if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
770772
GraphRow *pRow;
771773
int i;
@@ -780,11 +782,12 @@
780782
showArrowheads = skin_detail_boolean("timeline-arrowheads");
781783
circleNodes = skin_detail_boolean("timeline-circle-nodes");
782784
colorGraph = skin_detail_boolean("timeline-color-graph-lines");
783785
iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
784786
785
- @ <script id='timeline-data' type='application/json'>{
787
+ @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
788
+ @ "iTableId": %d(iTableId),
786789
@ "circleNodes": %d(circleNodes),
787790
@ "showArrowheads": %d(showArrowheads),
788791
@ "iRailPitch": %d(iRailPitch),
789792
@ "colorGraph": %d(colorGraph),
790793
@ "nomo": %d(PB("nomo")),
@@ -863,11 +866,11 @@
863866
if( cSep=='[' ) cgi_printf("[");
864867
cgi_printf("],\"h\":\"%!S\"}%s",
865868
pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
866869
}
867870
@ }</script>
868
- style_load_one_js_file("graph.js");
871
+ style_graph_generator();
869872
graph_free(pGraph);
870873
}
871874
}
872875
873876
/*
874877
--- src/timeline.c
+++ src/timeline.c
@@ -255,10 +255,11 @@
255 int vid = 0; /* Current checkout version */
256 int dateFormat = 0; /* 0: HH:MM (default) */
257 int bCommentGitStyle = 0; /* Only show comments through first blank line */
258 const char *zStyle; /* Sub-name for classes for the style */
259 const char *zDateFmt;
 
260
261 if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
262 vid = db_lget_int("checkout", 0);
263 }
264 zPrevDate[0] = 0;
@@ -285,11 +286,11 @@
285 db_static_prepare(&qbranch,
286 "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
287 TAG_BRANCH
288 );
289
290 @ <table id="timelineTable" class="timelineTable">
291 blob_zero(&comment);
292 while( db_step(pQuery)==SQLITE_ROW ){
293 int rid = db_column_int(pQuery, 0);
294 const char *zUuid = db_column_text(pQuery, 1);
295 int isLeaf = db_column_int(pQuery, 5);
@@ -721,11 +722,11 @@
721 @ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
722 }
723 }
724 @ </table>
725 if( fchngQueryInit ) db_finalize(&fchngQuery);
726 timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, 0);
727 }
728
729 /*
730 ** Change the RGB background color given in the argument in a foreground
731 ** color with the same hue.
@@ -762,10 +763,11 @@
762 ** graph.
763 */
764 void timeline_output_graph_javascript(
765 GraphContext *pGraph, /* The graph to be displayed */
766 int omitDescenders, /* True to omit descenders */
 
767 int fileDiff /* True for file diff. False for check-in diff */
768 ){
769 if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
770 GraphRow *pRow;
771 int i;
@@ -780,11 +782,12 @@
780 showArrowheads = skin_detail_boolean("timeline-arrowheads");
781 circleNodes = skin_detail_boolean("timeline-circle-nodes");
782 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
783 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
784
785 @ <script id='timeline-data' type='application/json'>{
 
786 @ "circleNodes": %d(circleNodes),
787 @ "showArrowheads": %d(showArrowheads),
788 @ "iRailPitch": %d(iRailPitch),
789 @ "colorGraph": %d(colorGraph),
790 @ "nomo": %d(PB("nomo")),
@@ -863,11 +866,11 @@
863 if( cSep=='[' ) cgi_printf("[");
864 cgi_printf("],\"h\":\"%!S\"}%s",
865 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
866 }
867 @ }</script>
868 style_load_one_js_file("graph.js");
869 graph_free(pGraph);
870 }
871 }
872
873 /*
874
--- src/timeline.c
+++ src/timeline.c
@@ -255,10 +255,11 @@
255 int vid = 0; /* Current checkout version */
256 int dateFormat = 0; /* 0: HH:MM (default) */
257 int bCommentGitStyle = 0; /* Only show comments through first blank line */
258 const char *zStyle; /* Sub-name for classes for the style */
259 const char *zDateFmt;
260 int iTableId = timeline_tableid();
261
262 if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
263 vid = db_lget_int("checkout", 0);
264 }
265 zPrevDate[0] = 0;
@@ -285,11 +286,11 @@
286 db_static_prepare(&qbranch,
287 "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
288 TAG_BRANCH
289 );
290
291 @ <table id="timelineTable%d(iTableId)" class="timelineTable">
292 blob_zero(&comment);
293 while( db_step(pQuery)==SQLITE_ROW ){
294 int rid = db_column_int(pQuery, 0);
295 const char *zUuid = db_column_text(pQuery, 1);
296 int isLeaf = db_column_int(pQuery, 5);
@@ -721,11 +722,11 @@
722 @ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
723 }
724 }
725 @ </table>
726 if( fchngQueryInit ) db_finalize(&fchngQuery);
727 timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, iTableId, 0);
728 }
729
730 /*
731 ** Change the RGB background color given in the argument in a foreground
732 ** color with the same hue.
@@ -762,10 +763,11 @@
763 ** graph.
764 */
765 void timeline_output_graph_javascript(
766 GraphContext *pGraph, /* The graph to be displayed */
767 int omitDescenders, /* True to omit descenders */
768 int iTableId, /* Which graph is this for */
769 int fileDiff /* True for file diff. False for check-in diff */
770 ){
771 if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
772 GraphRow *pRow;
773 int i;
@@ -780,11 +782,12 @@
782 showArrowheads = skin_detail_boolean("timeline-arrowheads");
783 circleNodes = skin_detail_boolean("timeline-circle-nodes");
784 colorGraph = skin_detail_boolean("timeline-color-graph-lines");
785 iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
786
787 @ <script id='timeline-data-%d(iTableId)' type='application/json'>{
788 @ "iTableId": %d(iTableId),
789 @ "circleNodes": %d(circleNodes),
790 @ "showArrowheads": %d(showArrowheads),
791 @ "iRailPitch": %d(iRailPitch),
792 @ "colorGraph": %d(colorGraph),
793 @ "nomo": %d(PB("nomo")),
@@ -863,11 +866,11 @@
866 if( cSep=='[' ) cgi_printf("[");
867 cgi_printf("],\"h\":\"%!S\"}%s",
868 pRow->zUuid, pRow->pNext ? ",\n" : "]\n");
869 }
870 @ }</script>
871 style_graph_generator();
872 graph_free(pGraph);
873 }
874 }
875
876 /*
877

Keyboard Shortcuts

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