##// END OF EJS Templates
hgweb: stop adding strings to innerHTML of #graphnodes and #nodebgs (BC)...
av6 -
r35551:8b958d21 default
parent child Browse files
Show More
@@ -1,513 +1,503
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.bg = [0, 4];
33 33 this.cell = [2, 0];
34 34 this.columns = 0;
35 35
36 36 }
37 37
38 38 Graph.prototype = {
39 39 reset: function() {
40 40 this.bg = [0, 4];
41 41 this.cell = [2, 0];
42 42 this.columns = 0;
43 document.getElementById('nodebgs').innerHTML = '';
44 43 },
45 44
46 45 scale: function(height) {
47 46 this.bg_height = height;
48 47 this.box_size = Math.floor(this.bg_height / 1.2);
49 48 this.cell_height = this.box_size;
50 49 },
51 50
52 51 setColor: function(color, bg, fg) {
53 52
54 53 // Set the colour.
55 54 //
56 55 // If color is a string, expect an hexadecimal RGB
57 56 // value and apply it unchanged. If color is a number,
58 57 // pick a distinct colour based on an internal wheel;
59 58 // the bg parameter provides the value that should be
60 59 // assigned to the 'zero' colours and the fg parameter
61 60 // provides the multiplier that should be applied to
62 61 // the foreground colours.
63 62 var s;
64 63 if(typeof color === "string") {
65 64 s = "#" + color;
66 65 } else { //typeof color === "number"
67 66 color %= colors.length;
68 67 var red = (colors[color][0] * fg) || bg;
69 68 var green = (colors[color][1] * fg) || bg;
70 69 var blue = (colors[color][2] * fg) || bg;
71 70 red = Math.round(red * 255);
72 71 green = Math.round(green * 255);
73 72 blue = Math.round(blue * 255);
74 73 s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
75 74 }
76 75 this.ctx.strokeStyle = s;
77 76 this.ctx.fillStyle = s;
78 77 return s;
79 78
80 79 },
81 80
82 81 edge: function(x0, y0, x1, y1, color, width) {
83 82
84 83 this.setColor(color, 0.0, 0.65);
85 84 if(width >= 0)
86 85 this.ctx.lineWidth = width;
87 86 this.ctx.beginPath();
88 87 this.ctx.moveTo(x0, y0);
89 88 this.ctx.lineTo(x1, y1);
90 89 this.ctx.stroke();
91 90
92 91 },
93 92
94 93 vertex: function(x, y, radius, color, parity, cur) {
95 94 this.ctx.beginPath();
96 95 this.setColor(color, 0.25, 0.75);
97 96 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
98 97 this.ctx.fill();
99 98
100 99 var left = (this.bg_height - this.box_size) + (this.columns + 1) * this.box_size;
101 100 var item = document.querySelector('[data-node="' + cur.node + '"]');
102 101 if (item) {
103 102 item.style.paddingLeft = left + 'px';
104 103 }
105
106 return ['', ''];
107 104 },
108 105
109 106 render: function(data) {
110 107
111 var backgrounds = '';
112 var nodedata = '';
113 108 var i, j, cur, line, start, end, color, x, y, x0, y0, x1, y1, column, radius;
114 109
115 110 var cols = 0;
116 111 for (i = 0; i < data.length; i++) {
117 112 cur = data[i];
118 113 for (j = 0; j < cur.edges.length; j++) {
119 114 line = cur.edges[j];
120 115 cols = Math.max(cols, line[0], line[1]);
121 116 }
122 117 }
123 118 this.canvas.width = (cols + 1) * this.bg_height;
124 119 this.canvas.height = (data.length + 1) * this.bg_height - 27;
125 120
126 121 for (i = 0; i < data.length; i++) {
127 122
128 123 var parity = i % 2;
129 124 this.cell[1] += this.bg_height;
130 125 this.bg[1] += this.bg_height;
131 126
132 127 cur = data[i];
133 128 var fold = false;
134 129
135 130 var prevWidth = this.ctx.lineWidth;
136 131 for (j = 0; j < cur.edges.length; j++) {
137 132
138 133 line = cur.edges[j];
139 134 start = line[0];
140 135 end = line[1];
141 136 color = line[2];
142 137 var width = line[3];
143 138 if(width < 0)
144 139 width = prevWidth;
145 140 var branchcolor = line[4];
146 141 if(branchcolor)
147 142 color = branchcolor;
148 143
149 144 if (end > this.columns || start > this.columns) {
150 145 this.columns += 1;
151 146 }
152 147
153 148 if (start === this.columns && start > end) {
154 149 fold = true;
155 150 }
156 151
157 152 x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
158 153 y0 = this.bg[1] - this.bg_height / 2;
159 154 x1 = this.cell[0] + this.box_size * end + this.box_size / 2;
160 155 y1 = this.bg[1] + this.bg_height / 2;
161 156
162 157 this.edge(x0, y0, x1, y1, color, width);
163 158
164 159 }
165 160 this.ctx.lineWidth = prevWidth;
166 161
167 162 // Draw the revision node in the right column
168 163
169 164 column = cur.vertex[0];
170 165 color = cur.vertex[1];
171 166
172 167 radius = this.box_size / 8;
173 168 x = this.cell[0] + this.box_size * column + this.box_size / 2;
174 169 y = this.bg[1] - this.bg_height / 2;
175 var add = this.vertex(x, y, radius, color, parity, cur);
176 backgrounds += add[0];
177 nodedata += add[1];
170 this.vertex(x, y, radius, color, parity, cur);
178 171
179 172 if (fold) this.columns -= 1;
180 173
181 174 }
182 175
183 document.getElementById('nodebgs').innerHTML += backgrounds;
184 document.getElementById('graphnodes').innerHTML += nodedata;
185
186 176 }
187 177
188 178 };
189 179
190 180
191 181 function process_dates(parentSelector){
192 182
193 183 // derived from code from mercurial/templatefilter.py
194 184
195 185 var scales = {
196 186 'year': 365 * 24 * 60 * 60,
197 187 'month': 30 * 24 * 60 * 60,
198 188 'week': 7 * 24 * 60 * 60,
199 189 'day': 24 * 60 * 60,
200 190 'hour': 60 * 60,
201 191 'minute': 60,
202 192 'second': 1
203 193 };
204 194
205 195 function format(count, string){
206 196 var ret = count + ' ' + string;
207 197 if (count > 1){
208 198 ret = ret + 's';
209 199 }
210 200 return ret;
211 201 }
212 202
213 203 function shortdate(date){
214 204 var ret = date.getFullYear() + '-';
215 205 // getMonth() gives a 0-11 result
216 206 var month = date.getMonth() + 1;
217 207 if (month <= 9){
218 208 ret += '0' + month;
219 209 } else {
220 210 ret += month;
221 211 }
222 212 ret += '-';
223 213 var day = date.getDate();
224 214 if (day <= 9){
225 215 ret += '0' + day;
226 216 } else {
227 217 ret += day;
228 218 }
229 219 return ret;
230 220 }
231 221
232 222 function age(datestr){
233 223 var now = new Date();
234 224 var once = new Date(datestr);
235 225 if (isNaN(once.getTime())){
236 226 // parsing error
237 227 return datestr;
238 228 }
239 229
240 230 var delta = Math.floor((now.getTime() - once.getTime()) / 1000);
241 231
242 232 var future = false;
243 233 if (delta < 0){
244 234 future = true;
245 235 delta = -delta;
246 236 if (delta > (30 * scales.year)){
247 237 return "in the distant future";
248 238 }
249 239 }
250 240
251 241 if (delta > (2 * scales.year)){
252 242 return shortdate(once);
253 243 }
254 244
255 245 for (var unit in scales){
256 246 if (!scales.hasOwnProperty(unit)) { continue; }
257 247 var s = scales[unit];
258 248 var n = Math.floor(delta / s);
259 249 if ((n >= 2) || (s === 1)){
260 250 if (future){
261 251 return format(n, unit) + ' from now';
262 252 } else {
263 253 return format(n, unit) + ' ago';
264 254 }
265 255 }
266 256 }
267 257 }
268 258
269 259 var nodes = document.querySelectorAll((parentSelector || '') + ' .age');
270 260 var dateclass = new RegExp('\\bdate\\b');
271 261 for (var i=0; i<nodes.length; ++i){
272 262 var node = nodes[i];
273 263 var classes = node.className;
274 264 var agevalue = age(node.textContent);
275 265 if (dateclass.test(classes)){
276 266 // We want both: date + (age)
277 267 node.textContent += ' ('+agevalue+')';
278 268 } else {
279 269 node.title = node.textContent;
280 270 node.textContent = agevalue;
281 271 }
282 272 }
283 273 }
284 274
285 275 function toggleDiffstat() {
286 276 var curdetails = document.getElementById('diffstatdetails').style.display;
287 277 var curexpand = curdetails === 'none' ? 'inline' : 'none';
288 278 document.getElementById('diffstatdetails').style.display = curexpand;
289 279 document.getElementById('diffstatexpand').style.display = curdetails;
290 280 }
291 281
292 282 function toggleLinewrap() {
293 283 function getLinewrap() {
294 284 var nodes = document.getElementsByClassName('sourcelines');
295 285 // if there are no such nodes, error is thrown here
296 286 return nodes[0].classList.contains('wrap');
297 287 }
298 288
299 289 function setLinewrap(enable) {
300 290 var nodes = document.getElementsByClassName('sourcelines');
301 291 var i;
302 292 for (i = 0; i < nodes.length; i++) {
303 293 if (enable) {
304 294 nodes[i].classList.add('wrap');
305 295 } else {
306 296 nodes[i].classList.remove('wrap');
307 297 }
308 298 }
309 299
310 300 var links = document.getElementsByClassName('linewraplink');
311 301 for (i = 0; i < links.length; i++) {
312 302 links[i].innerHTML = enable ? 'on' : 'off';
313 303 }
314 304 }
315 305
316 306 setLinewrap(!getLinewrap());
317 307 }
318 308
319 309 function format(str, replacements) {
320 310 return str.replace(/%(\w+)%/g, function(match, p1) {
321 311 return String(replacements[p1]);
322 312 });
323 313 }
324 314
325 315 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) {
326 316 var xhr = new XMLHttpRequest();
327 317 xhr.onreadystatechange = function() {
328 318 if (xhr.readyState === 4) {
329 319 try {
330 320 if (xhr.status === 200) {
331 321 onsuccess(xhr.responseText);
332 322 } else {
333 323 throw 'server error';
334 324 }
335 325 } catch (e) {
336 326 onerror(e);
337 327 } finally {
338 328 oncomplete();
339 329 }
340 330 }
341 331 };
342 332
343 333 xhr.open(method, url);
344 334 xhr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase());
345 335 xhr.send();
346 336 onstart();
347 337 return xhr;
348 338 }
349 339
350 340 function removeByClassName(className) {
351 341 var nodes = document.getElementsByClassName(className);
352 342 while (nodes.length) {
353 343 nodes[0].parentNode.removeChild(nodes[0]);
354 344 }
355 345 }
356 346
357 347 function docFromHTML(html) {
358 348 var doc = document.implementation.createHTMLDocument('');
359 349 doc.documentElement.innerHTML = html;
360 350 return doc;
361 351 }
362 352
363 353 function appendFormatHTML(element, formatStr, replacements) {
364 354 element.insertAdjacentHTML('beforeend', format(formatStr, replacements));
365 355 }
366 356
367 357 function adoptChildren(from, to) {
368 358 var nodes = from.children;
369 359 var curClass = 'c' + Date.now();
370 360 while (nodes.length) {
371 361 var node = nodes[0];
372 362 node = document.adoptNode(node);
373 363 node.classList.add(curClass);
374 364 to.appendChild(node);
375 365 }
376 366 process_dates('.' + curClass);
377 367 }
378 368
379 369 function ajaxScrollInit(urlFormat,
380 370 nextPageVar,
381 371 nextPageVarGet,
382 372 containerSelector,
383 373 messageFormat,
384 374 mode) {
385 375 var updateInitiated = false;
386 376 var container = document.querySelector(containerSelector);
387 377
388 378 function scrollHandler() {
389 379 if (updateInitiated) {
390 380 return;
391 381 }
392 382
393 383 var scrollHeight = document.documentElement.scrollHeight;
394 384 var clientHeight = document.documentElement.clientHeight;
395 385 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
396 386
397 387 if (scrollHeight - (scrollTop + clientHeight) < 50) {
398 388 updateInitiated = true;
399 389 removeByClassName('scroll-loading-error');
400 390 container.lastElementChild.classList.add('scroll-separator');
401 391
402 392 if (!nextPageVar) {
403 393 var message = {
404 394 'class': 'scroll-loading-info',
405 395 text: 'No more entries'
406 396 };
407 397 appendFormatHTML(container, messageFormat, message);
408 398 return;
409 399 }
410 400
411 401 makeRequest(
412 402 format(urlFormat, {next: nextPageVar}),
413 403 'GET',
414 404 function onstart() {
415 405 var message = {
416 406 'class': 'scroll-loading',
417 407 text: 'Loading...'
418 408 };
419 409 appendFormatHTML(container, messageFormat, message);
420 410 },
421 411 function onsuccess(htmlText) {
422 412 var doc = docFromHTML(htmlText);
423 413
424 414 if (mode === 'graph') {
425 415 var graph = window.graph;
426 416 var dataStr = htmlText.match(/^\s*var data = (.*);$/m)[1];
427 417 var data = JSON.parse(dataStr);
428 418 if (data.length < nextPageVar) {
429 419 nextPageVar = undefined;
430 420 }
431 421 graph.reset();
432 422 adoptChildren(doc.querySelector('#graphnodes'), container.querySelector('#graphnodes'));
433 423 graph.render(data);
434 424 } else {
435 425 adoptChildren(doc.querySelector(containerSelector), container);
436 426 }
437 427
438 428 nextPageVar = nextPageVarGet(htmlText, nextPageVar);
439 429 },
440 430 function onerror(errorText) {
441 431 var message = {
442 432 'class': 'scroll-loading-error',
443 433 text: 'Error: ' + errorText
444 434 };
445 435 appendFormatHTML(container, messageFormat, message);
446 436 },
447 437 function oncomplete() {
448 438 removeByClassName('scroll-loading');
449 439 updateInitiated = false;
450 440 scrollHandler();
451 441 }
452 442 );
453 443 }
454 444 }
455 445
456 446 window.addEventListener('scroll', scrollHandler);
457 447 window.addEventListener('resize', scrollHandler);
458 448 scrollHandler();
459 449 }
460 450
461 451 function renderDiffOptsForm() {
462 452 // We use URLSearchParams for query string manipulation. Old browsers don't
463 453 // support this API.
464 454 if (!("URLSearchParams" in window)) {
465 455 return;
466 456 }
467 457
468 458 var form = document.getElementById("diffopts-form");
469 459
470 460 var KEYS = [
471 461 "ignorews",
472 462 "ignorewsamount",
473 463 "ignorewseol",
474 464 "ignoreblanklines",
475 465 ];
476 466
477 467 var urlParams = new window.URLSearchParams(window.location.search);
478 468
479 469 function updateAndRefresh(e) {
480 470 var checkbox = e.target;
481 471 var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
482 472 urlParams.set(name, checkbox.checked ? "1" : "0");
483 473 window.location.search = urlParams.toString();
484 474 }
485 475
486 476 var allChecked = form.getAttribute("data-ignorews") === "1";
487 477
488 478 for (var i = 0; i < KEYS.length; i++) {
489 479 var key = KEYS[i];
490 480
491 481 var checkbox = document.getElementById(key + "-checkbox");
492 482 if (!checkbox) {
493 483 continue;
494 484 }
495 485
496 486 var currentValue = form.getAttribute("data-" + key);
497 487 checkbox.checked = currentValue !== "0";
498 488
499 489 // ignorews implies ignorewsamount and ignorewseol.
500 490 if (allChecked && (key === "ignorewsamount" || key === "ignorewseol")) {
501 491 checkbox.checked = true;
502 492 checkbox.disabled = true;
503 493 }
504 494
505 495 checkbox.addEventListener("change", updateAndRefresh, false);
506 496 }
507 497
508 498 form.style.display = 'block';
509 499 }
510 500
511 501 document.addEventListener('DOMContentLoaded', function() {
512 502 process_dates();
513 503 }, false);
General Comments 0
You need to be logged in to leave comments. Login now