##// END OF EJS Templates
hgweb: add graph mode of ajax response processing...
Alexander Plavin -
r19782:8123f505 default
parent child Browse files
Show More
@@ -1,430 +1,442 b''
1 1 // mercurial.js - JavaScript utility functions
2 2 //
3 3 // Rendering of branch DAGs on the client side
4 4 // Display of elapsed time
5 5 // Show or hide diffstat
6 6 //
7 7 // Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl>
8 8 // Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de>
9 9 //
10 10 // derived from code written by Scott James Remnant <scott@ubuntu.com>
11 11 // Copyright 2005 Canonical Ltd.
12 12 //
13 13 // This software may be used and distributed according to the terms
14 14 // of the GNU General Public License, incorporated herein by reference.
15 15
16 16 var colors = [
17 17 [ 1.0, 0.0, 0.0 ],
18 18 [ 1.0, 1.0, 0.0 ],
19 19 [ 0.0, 1.0, 0.0 ],
20 20 [ 0.0, 1.0, 1.0 ],
21 21 [ 0.0, 0.0, 1.0 ],
22 22 [ 1.0, 0.0, 1.0 ]
23 23 ];
24 24
25 25 function Graph() {
26 26
27 27 this.canvas = document.getElementById('graph');
28 28 if (window.G_vmlCanvasManager) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
29 29 this.ctx = this.canvas.getContext('2d');
30 30 this.ctx.strokeStyle = 'rgb(0, 0, 0)';
31 31 this.ctx.fillStyle = 'rgb(0, 0, 0)';
32 32 this.cur = [0, 0];
33 33 this.line_width = 3;
34 34 this.bg = [0, 4];
35 35 this.cell = [2, 0];
36 36 this.columns = 0;
37 37 this.revlink = '';
38 38
39 39 this.reset = function() {
40 40 this.bg = [0, 4];
41 41 this.cell = [2, 0];
42 42 this.columns = 0;
43 43 document.getElementById('nodebgs').innerHTML = '';
44 44 document.getElementById('graphnodes').innerHTML = '';
45 45 }
46 46
47 47 this.scale = function(height) {
48 48 this.bg_height = height;
49 49 this.box_size = Math.floor(this.bg_height / 1.2);
50 50 this.cell_height = this.box_size;
51 51 }
52 52
53 53 function colorPart(num) {
54 54 num *= 255
55 55 num = num < 0 ? 0 : num;
56 56 num = num > 255 ? 255 : num;
57 57 var digits = Math.round(num).toString(16);
58 58 if (num < 16) {
59 59 return '0' + digits;
60 60 } else {
61 61 return digits;
62 62 }
63 63 }
64 64
65 65 this.setColor = function(color, bg, fg) {
66 66
67 67 // Set the colour.
68 68 //
69 69 // If color is a string, expect an hexadecimal RGB
70 70 // value and apply it unchanged. If color is a number,
71 71 // pick a distinct colour based on an internal wheel;
72 72 // the bg parameter provides the value that should be
73 73 // assigned to the 'zero' colours and the fg parameter
74 74 // provides the multiplier that should be applied to
75 75 // the foreground colours.
76 76 var s;
77 77 if(typeof color == "string") {
78 78 s = "#" + color;
79 79 } else { //typeof color == "number"
80 80 color %= colors.length;
81 81 var red = (colors[color][0] * fg) || bg;
82 82 var green = (colors[color][1] * fg) || bg;
83 83 var blue = (colors[color][2] * fg) || bg;
84 84 red = Math.round(red * 255);
85 85 green = Math.round(green * 255);
86 86 blue = Math.round(blue * 255);
87 87 s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
88 88 }
89 89 this.ctx.strokeStyle = s;
90 90 this.ctx.fillStyle = s;
91 91 return s;
92 92
93 93 }
94 94
95 95 this.edge = function(x0, y0, x1, y1, color, width) {
96 96
97 97 this.setColor(color, 0.0, 0.65);
98 98 if(width >= 0)
99 99 this.ctx.lineWidth = width;
100 100 this.ctx.beginPath();
101 101 this.ctx.moveTo(x0, y0);
102 102 this.ctx.lineTo(x1, y1);
103 103 this.ctx.stroke();
104 104
105 105 }
106 106
107 107 this.render = function(data) {
108 108
109 109 var backgrounds = '';
110 110 var nodedata = '';
111 111
112 112 for (var i in data) {
113 113
114 114 var parity = i % 2;
115 115 this.cell[1] += this.bg_height;
116 116 this.bg[1] += this.bg_height;
117 117
118 118 var cur = data[i];
119 119 var node = cur[1];
120 120 var edges = cur[2];
121 121 var fold = false;
122 122
123 123 var prevWidth = this.ctx.lineWidth;
124 124 for (var j in edges) {
125 125
126 126 line = edges[j];
127 127 start = line[0];
128 128 end = line[1];
129 129 color = line[2];
130 130 var width = line[3];
131 131 if(width < 0)
132 132 width = prevWidth;
133 133 var branchcolor = line[4];
134 134 if(branchcolor)
135 135 color = branchcolor;
136 136
137 137 if (end > this.columns || start > this.columns) {
138 138 this.columns += 1;
139 139 }
140 140
141 141 if (start == this.columns && start > end) {
142 142 var fold = true;
143 143 }
144 144
145 145 x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
146 146 y0 = this.bg[1] - this.bg_height / 2;
147 147 x1 = this.cell[0] + this.box_size * end + this.box_size / 2;
148 148 y1 = this.bg[1] + this.bg_height / 2;
149 149
150 150 this.edge(x0, y0, x1, y1, color, width);
151 151
152 152 }
153 153 this.ctx.lineWidth = prevWidth;
154 154
155 155 // Draw the revision node in the right column
156 156
157 157 column = node[0]
158 158 color = node[1]
159 159
160 160 radius = this.box_size / 8;
161 161 x = this.cell[0] + this.box_size * column + this.box_size / 2;
162 162 y = this.bg[1] - this.bg_height / 2;
163 163 var add = this.vertex(x, y, color, parity, cur);
164 164 backgrounds += add[0];
165 165 nodedata += add[1];
166 166
167 167 if (fold) this.columns -= 1;
168 168
169 169 }
170 170
171 171 document.getElementById('nodebgs').innerHTML += backgrounds;
172 172 document.getElementById('graphnodes').innerHTML += nodedata;
173 173
174 174 }
175 175
176 176 }
177 177
178 178
179 179 process_dates = (function(document, RegExp, Math, isNaN, Date, _false, _true){
180 180
181 181 // derived from code from mercurial/templatefilter.py
182 182
183 183 var scales = {
184 184 'year': 365 * 24 * 60 * 60,
185 185 'month': 30 * 24 * 60 * 60,
186 186 'week': 7 * 24 * 60 * 60,
187 187 'day': 24 * 60 * 60,
188 188 'hour': 60 * 60,
189 189 'minute': 60,
190 190 'second': 1
191 191 };
192 192
193 193 function format(count, string){
194 194 var ret = count + ' ' + string;
195 195 if (count > 1){
196 196 ret = ret + 's';
197 197 }
198 198 return ret;
199 199 }
200 200
201 201 function shortdate(date){
202 202 var ret = date.getFullYear() + '-';
203 203 // getMonth() gives a 0-11 result
204 204 var month = date.getMonth() + 1;
205 205 if (month <= 9){
206 206 ret += '0' + month;
207 207 } else {
208 208 ret += month;
209 209 }
210 210 ret += '-';
211 211 var day = date.getDate();
212 212 if (day <= 9){
213 213 ret += '0' + day;
214 214 } else {
215 215 ret += day;
216 216 }
217 217 return ret;
218 218 }
219 219
220 220 function age(datestr){
221 221 var now = new Date();
222 222 var once = new Date(datestr);
223 223 if (isNaN(once.getTime())){
224 224 // parsing error
225 225 return datestr;
226 226 }
227 227
228 228 var delta = Math.floor((now.getTime() - once.getTime()) / 1000);
229 229
230 230 var future = _false;
231 231 if (delta < 0){
232 232 future = _true;
233 233 delta = -delta;
234 234 if (delta > (30 * scales.year)){
235 235 return "in the distant future";
236 236 }
237 237 }
238 238
239 239 if (delta > (2 * scales.year)){
240 240 return shortdate(once);
241 241 }
242 242
243 243 for (unit in scales){
244 244 var s = scales[unit];
245 245 var n = Math.floor(delta / s);
246 246 if ((n >= 2) || (s == 1)){
247 247 if (future){
248 248 return format(n, unit) + ' from now';
249 249 } else {
250 250 return format(n, unit) + ' ago';
251 251 }
252 252 }
253 253 }
254 254 }
255 255
256 256 return function(){
257 257 var nodes = document.getElementsByTagName('*');
258 258 var ageclass = new RegExp('\\bage\\b');
259 259 var dateclass = new RegExp('\\bdate\\b');
260 260 for (var i=0; i<nodes.length; ++i){
261 261 var node = nodes[i];
262 262 var classes = node.className;
263 263 if (ageclass.test(classes)){
264 264 var agevalue = age(node.textContent);
265 265 if (dateclass.test(classes)){
266 266 // We want both: date + (age)
267 267 node.textContent += ' ('+agevalue+')';
268 268 } else {
269 269 node.title = node.textContent;
270 270 node.textContent = agevalue;
271 271 }
272 272 }
273 273 }
274 274 }
275 275 })(document, RegExp, Math, isNaN, Date, false, true)
276 276
277 277 function toggleDiffstat() {
278 278 var curdetails = document.getElementById('diffstatdetails').style.display;
279 279 var curexpand = curdetails == 'none' ? 'inline' : 'none';
280 280 document.getElementById('diffstatdetails').style.display = curexpand;
281 281 document.getElementById('diffstatexpand').style.display = curdetails;
282 282 }
283 283
284 284 function toggleLinewrap() {
285 285 function getLinewrap() {
286 286 var nodes = document.getElementsByClassName('sourcelines');
287 287 // if there are no such nodes, error is thrown here
288 288 return nodes[0].classList.contains('wrap');
289 289 }
290 290
291 291 function setLinewrap(enable) {
292 292 var nodes = document.getElementsByClassName('sourcelines');
293 293 for (var i = 0; i < nodes.length; i++) {
294 294 if (enable) {
295 295 nodes[i].classList.add('wrap');
296 296 } else {
297 297 nodes[i].classList.remove('wrap');
298 298 }
299 299 }
300 300
301 301 var links = document.getElementsByClassName('linewraplink');
302 302 for (var i = 0; i < links.length; i++) {
303 303 links[i].innerHTML = enable ? 'on' : 'off';
304 304 }
305 305 }
306 306
307 307 setLinewrap(!getLinewrap());
308 308 }
309 309
310 310 function format(str, replacements) {
311 311 return str.replace(/%(\w+)%/g, function(match, p1) {
312 312 return String(replacements[p1]);
313 313 });
314 314 }
315 315
316 316 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) {
317 317 xfr = new XMLHttpRequest();
318 318 xfr.onreadystatechange = function() {
319 319 if (xfr.readyState === 4) {
320 320 try {
321 321 if (xfr.status === 200) {
322 322 onsuccess(xfr.responseText);
323 323 } else {
324 324 throw 'server error';
325 325 }
326 326 } catch (e) {
327 327 onerror(e);
328 328 } finally {
329 329 oncomplete();
330 330 }
331 331 }
332 332 };
333 333
334 334 xfr.open(method, url);
335 335 xfr.send();
336 336 onstart();
337 337 return xfr;
338 338 }
339 339
340 340 function removeByClassName(className) {
341 341 var nodes = document.getElementsByClassName(className);
342 342 while (nodes.length) {
343 343 nodes[0].parentNode.removeChild(nodes[0]);
344 344 }
345 345 }
346 346
347 347 function docFromHTML(html) {
348 348 var doc = document.implementation.createHTMLDocument('');
349 349 doc.documentElement.innerHTML = html;
350 350 return doc;
351 351 }
352 352
353 353 function appendFormatHTML(element, formatStr, replacements) {
354 354 element.insertAdjacentHTML('beforeend', format(formatStr, replacements));
355 355 }
356 356
357 357 function ajaxScrollInit(urlFormat,
358 358 nextPageVar,
359 359 nextPageVarGet,
360 360 containerSelector,
361 messageFormat) {
361 messageFormat,
362 mode) {
362 363 updateInitiated = false;
363 364 container = document.querySelector(containerSelector);
364 365
365 366 function scrollHandler() {
366 367 if (updateInitiated) {
367 368 return;
368 369 }
369 370
370 371 var scrollHeight = document.documentElement.scrollHeight;
371 372 var clientHeight = document.documentElement.clientHeight;
372 373 var scrollTop = document.body.scrollTop
373 374 || document.documentElement.scrollTop;
374 375
375 376 if (scrollHeight - (scrollTop + clientHeight) < 50) {
376 377 updateInitiated = true;
377 378 removeByClassName('scroll-loading-error');
378 379 container.lastElementChild.classList.add('scroll-separator');
379 380
380 381 if (!nextPageVar) {
381 382 var message = {
382 383 class: 'scroll-loading-info',
383 384 text: 'No more entries'
384 385 };
385 386 appendFormatHTML(container, messageFormat, message);
386 387 return;
387 388 }
388 389
389 390 makeRequest(
390 391 format(urlFormat, {next: nextPageVar}),
391 392 'GET',
392 393 function onstart() {
393 394 var message = {
394 395 class: 'scroll-loading',
395 396 text: 'Loading...'
396 397 };
397 398 appendFormatHTML(container, messageFormat, message);
398 399 },
399 400 function onsuccess(htmlText) {
400 401 nextPageVar = nextPageVarGet(htmlText, nextPageVar);
401 402
402 var doc = docFromHTML(htmlText);
403 var nodes = doc.querySelector(containerSelector).children;
404 while (nodes.length) {
405 var node = nodes[0];
406 node = document.adoptNode(node);
407 container.appendChild(node);
403 if (mode == 'graph') {
404 var addHeight = htmlText.match(/^<canvas id="graph".*height="(\d+)"><\/canvas>$/m)[1];
405 addHeight = parseInt(addHeight);
406 graph.canvas.height = addHeight;
407
408 var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1];
409 var data = JSON.parse(dataStr)
410 graph.reset();
411 graph.render(data);
412 } else {
413 var doc = docFromHTML(htmlText);
414 var nodes = doc.querySelector(containerSelector).children;
415 while (nodes.length) {
416 var node = nodes[0];
417 node = document.adoptNode(node);
418 container.appendChild(node);
419 }
420 process_dates();
408 421 }
409 process_dates();
410 422 },
411 423 function onerror(errorText) {
412 424 var message = {
413 425 class: 'scroll-loading-error',
414 426 text: 'Error: ' + errorText
415 427 };
416 428 appendFormatHTML(container, messageFormat, message);
417 429 },
418 430 function oncomplete() {
419 431 removeByClassName('scroll-loading');
420 432 updateInitiated = false;
421 433 scrollHandler();
422 434 }
423 435 );
424 436 }
425 437 }
426 438
427 439 window.addEventListener('scroll', scrollHandler);
428 440 window.addEventListener('resize', scrollHandler);
429 441 scrollHandler();
430 442 }
General Comments 0
You need to be logged in to leave comments. Login now