debian-mirror-gitlab/app/assets/javascripts/network/branch_graph.js

349 lines
9.8 KiB
JavaScript
Raw Normal View History

2020-03-13 15:44:24 +05:30
/* eslint-disable func-names, consistent-return */
2016-09-13 17:45:13 +05:30
2018-05-09 12:01:36 +05:30
import $ from 'jquery';
2018-03-17 18:26:18 +05:30
import { __ } from '../locale';
import axios from '../lib/utils/axios_utils';
2017-08-17 22:00:37 +05:30
import Raphael from './raphael';
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
export default class BranchGraph {
constructor(element1, options1) {
2017-08-17 22:00:37 +05:30
this.element = element1;
this.options = options1;
this.scrollTop = this.scrollTop.bind(this);
this.scrollBottom = this.scrollBottom.bind(this);
this.scrollRight = this.scrollRight.bind(this);
this.scrollLeft = this.scrollLeft.bind(this);
this.scrollUp = this.scrollUp.bind(this);
this.scrollDown = this.scrollDown.bind(this);
this.preparedCommits = {};
this.mtime = 0;
this.mspace = 0;
this.parents = {};
2018-12-13 13:39:08 +05:30
this.colors = ['#000'];
2017-08-17 22:00:37 +05:30
this.offsetX = 150;
this.offsetY = 20;
this.unitTime = 30;
this.unitSpace = 10;
this.prev_start = -1;
this.load();
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
load() {
2018-12-13 13:39:08 +05:30
axios
.get(this.options.url)
2018-03-17 18:26:18 +05:30
.then(({ data }) => {
2018-12-13 13:39:08 +05:30
$('.loading', this.element).hide();
2017-08-17 22:00:37 +05:30
this.prepareData(data.days, data.commits);
2018-03-17 18:26:18 +05:30
this.buildGraph();
})
.catch(() => __('Error fetching network graph.'));
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
prepareData(days, commits) {
2017-08-17 22:00:37 +05:30
this.days = days;
this.commits = commits;
this.collectParents();
this.graphHeight = $(this.element).height();
this.graphWidth = $(this.element).width();
2019-12-26 22:10:19 +05:30
const ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
const cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
2017-08-17 22:00:37 +05:30
this.r = Raphael(this.element.get(0), cw, ch);
this.top = this.r.set();
this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
2020-10-24 23:57:45 +05:30
this.commits = this.commits.reduce((acc, commit) => {
const updatedCommit = commit;
if (commit.id in this.parents) {
updatedCommit.isParent = true;
2016-09-13 17:45:13 +05:30
}
2020-10-24 23:57:45 +05:30
acc.push(updatedCommit);
this.preparedCommits[commit.id] = commit;
this.markCommit(commit);
return acc;
}, []);
2017-08-17 22:00:37 +05:30
return this.collectColors();
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
collectParents() {
const ref = this.commits;
const results = [];
2020-10-24 23:57:45 +05:30
ref.forEach(c => {
2017-08-17 22:00:37 +05:30
this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space);
2019-12-26 22:10:19 +05:30
const ref1 = c.parents;
const results1 = [];
2020-10-24 23:57:45 +05:30
ref1.forEach(p => {
2019-12-26 22:10:19 +05:30
this.parents[p[0]] = true;
results1.push((this.mspace = Math.max(this.mspace, p[1])));
2020-10-24 23:57:45 +05:30
});
2019-12-26 22:10:19 +05:30
results.push(results1);
2020-10-24 23:57:45 +05:30
});
2017-08-17 22:00:37 +05:30
return results;
2019-12-26 22:10:19 +05:30
}
2017-08-17 22:00:37 +05:30
2019-12-26 22:10:19 +05:30
collectColors() {
let k = 0;
const results = [];
2017-08-17 22:00:37 +05:30
while (k < this.mspace) {
2018-12-13 13:39:08 +05:30
this.colors.push(Raphael.getColor(0.8));
2017-08-17 22:00:37 +05:30
// Skipping a few colors in the spectrum to get more contrast between colors
Raphael.getColor();
Raphael.getColor();
2018-12-13 13:39:08 +05:30
results.push((k += 1));
2017-08-17 22:00:37 +05:30
}
return results;
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
buildGraph() {
let mm = 0;
let len = 0;
let cuday = 0;
let cumonth = '';
2018-11-08 19:23:39 +05:30
const { r } = this;
2017-08-17 22:00:37 +05:30
r.rect(0, 0, 40, this.barHeight).attr({
2018-12-13 13:39:08 +05:30
fill: '#222',
2017-08-17 22:00:37 +05:30
});
r.rect(40, 0, 30, this.barHeight).attr({
2018-12-13 13:39:08 +05:30
fill: '#444',
2017-08-17 22:00:37 +05:30
});
2019-12-26 22:10:19 +05:30
const ref = this.days;
2018-11-08 19:23:39 +05:30
for (mm = 0, len = ref.length; mm < len; mm += 1) {
2019-12-26 22:10:19 +05:30
const day = ref[mm];
2017-08-17 22:00:37 +05:30
if (cuday !== day[0] || cumonth !== day[1]) {
// Dates
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
2018-12-13 13:39:08 +05:30
font: '12px Monaco, monospace',
fill: '#BBB',
2017-08-17 22:00:37 +05:30
});
2018-11-08 19:23:39 +05:30
[cuday] = day;
2016-09-13 17:45:13 +05:30
}
2017-08-17 22:00:37 +05:30
if (cumonth !== day[1]) {
// Months
r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
2018-12-13 13:39:08 +05:30
font: '12px Monaco, monospace',
fill: '#EEE',
2017-08-17 22:00:37 +05:30
});
2018-11-08 19:23:39 +05:30
// eslint-disable-next-line prefer-destructuring
2017-08-17 22:00:37 +05:30
cumonth = day[1];
2016-09-13 17:45:13 +05:30
}
2017-08-17 22:00:37 +05:30
}
this.renderPartialGraph();
return this.bindEvents();
2019-12-26 22:10:19 +05:30
}
2017-08-17 22:00:37 +05:30
2019-12-26 22:10:19 +05:30
renderPartialGraph() {
const isGraphEdge = true;
let i = 0;
let start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
2017-08-17 22:00:37 +05:30
if (start < 0) {
start = 0;
}
2019-12-26 22:10:19 +05:30
let end = start + 40;
2017-08-17 22:00:37 +05:30
if (this.commits.length < end) {
end = this.commits.length;
}
if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
i = start;
this.prev_start = start;
while (i < end) {
2019-12-26 22:10:19 +05:30
const commit = this.commits[i];
2017-08-17 22:00:37 +05:30
i += 1;
if (commit.hasDrawn !== true) {
2019-12-26 22:10:19 +05:30
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
const y = this.offsetY + this.unitTime * commit.time;
2017-08-17 22:00:37 +05:30
this.drawDot(x, y, commit);
this.drawLines(x, y, commit);
this.appendLabel(x, y, commit);
this.appendAnchor(x, y, commit);
commit.hasDrawn = true;
2016-09-13 17:45:13 +05:30
}
}
2017-08-17 22:00:37 +05:30
return this.top.toFront();
}
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
bindEvents() {
2018-11-08 19:23:39 +05:30
const { element } = this;
2019-12-26 22:10:19 +05:30
return $(element).scroll(() => this.renderPartialGraph());
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollDown() {
2017-08-17 22:00:37 +05:30
this.element.scrollTop(this.element.scrollTop() + 50);
return this.renderPartialGraph();
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollUp() {
2017-08-17 22:00:37 +05:30
this.element.scrollTop(this.element.scrollTop() - 50);
return this.renderPartialGraph();
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollLeft() {
2017-08-17 22:00:37 +05:30
this.element.scrollLeft(this.element.scrollLeft() - 50);
return this.renderPartialGraph();
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollRight() {
2017-08-17 22:00:37 +05:30
this.element.scrollLeft(this.element.scrollLeft() + 50);
return this.renderPartialGraph();
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollBottom() {
2017-08-17 22:00:37 +05:30
return this.element.scrollTop(this.element.find('svg').height());
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
scrollTop() {
2017-08-17 22:00:37 +05:30
return this.element.scrollTop(0);
2019-12-26 22:10:19 +05:30
}
2018-11-08 19:23:39 +05:30
2019-12-26 22:10:19 +05:30
appendLabel(x, y, commit) {
2017-08-17 22:00:37 +05:30
if (!commit.refs) {
return;
}
2018-11-08 19:23:39 +05:30
const { r } = this;
2019-12-26 22:10:19 +05:30
let shortrefs = commit.refs;
2017-08-17 22:00:37 +05:30
// Truncate if longer than 15 chars
if (shortrefs.length > 17) {
2019-12-21 20:55:43 +05:30
shortrefs = `${shortrefs.substr(0, 15)}`;
2017-08-17 22:00:37 +05:30
}
2019-12-26 22:10:19 +05:30
const text = r.text(x + 4, y, shortrefs).attr({
2018-12-13 13:39:08 +05:30
'text-anchor': 'start',
font: '10px Monaco, monospace',
fill: '#FFF',
title: commit.refs,
2016-09-13 17:45:13 +05:30
});
2019-12-26 22:10:19 +05:30
const textbox = text.getBBox();
2017-08-17 22:00:37 +05:30
// Create rectangle based on the size of the textbox
2019-12-26 22:10:19 +05:30
const rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
2018-12-13 13:39:08 +05:30
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
2016-09-13 17:45:13 +05:30
});
2019-12-04 20:38:33 +05:30
// Generate the triangle right of the tag box
r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({
2018-12-13 13:39:08 +05:30
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
2016-09-13 17:45:13 +05:30
});
2019-12-26 22:10:19 +05:30
const label = r.set(rect, text);
2018-12-13 13:39:08 +05:30
label.transform(['t', -rect.getBBox().width - 15, 0]);
2017-08-17 22:00:37 +05:30
// Set text to front
return text.toFront();
2019-12-26 22:10:19 +05:30
}
2017-08-17 22:00:37 +05:30
2019-12-26 22:10:19 +05:30
appendAnchor(x, y, commit) {
2018-11-08 19:23:39 +05:30
const { r, top, options } = this;
2018-12-13 13:39:08 +05:30
const anchor = r
.circle(x, y, 10)
.attr({
fill: '#000',
opacity: 0,
cursor: 'pointer',
})
2019-12-21 20:55:43 +05:30
.click(() => window.open(options.commit_url.replace('%s', commit.id), '_blank'))
2018-12-13 13:39:08 +05:30
.hover(
function() {
this.tooltip = r.commitTooltip(x + 5, y, commit);
return top.push(this.tooltip.insertBefore(this));
},
function() {
return this.tooltip && this.tooltip.remove() && delete this.tooltip;
},
);
2017-08-17 22:00:37 +05:30
return top.push(anchor);
2019-12-26 22:10:19 +05:30
}
2016-09-13 17:45:13 +05:30
2019-12-26 22:10:19 +05:30
drawDot(x, y, commit) {
2018-11-08 19:23:39 +05:30
const { r } = this;
2017-08-17 22:00:37 +05:30
r.circle(x, y, 3).attr({
fill: this.colors[commit.space],
2018-12-13 13:39:08 +05:30
stroke: 'none',
2016-09-13 17:45:13 +05:30
});
2018-11-08 19:23:39 +05:30
2020-03-13 15:44:24 +05:30
const avatarBoxX = this.offsetX + this.unitSpace * this.mspace + 10;
const avatarBoxY = y - 10;
2018-11-08 19:23:39 +05:30
2020-03-13 15:44:24 +05:30
r.rect(avatarBoxX, avatarBoxY, 20, 20).attr({
2017-08-17 22:00:37 +05:30
stroke: this.colors[commit.space],
2018-12-13 13:39:08 +05:30
'stroke-width': 2,
2016-09-13 17:45:13 +05:30
});
2020-03-13 15:44:24 +05:30
r.image(commit.author.icon, avatarBoxX, avatarBoxY, 20, 20);
2018-12-13 13:39:08 +05:30
return r
.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0])
.attr({
'text-anchor': 'start',
font: '14px Monaco, monospace',
});
2019-12-26 22:10:19 +05:30
}
2017-08-17 22:00:37 +05:30
2019-12-26 22:10:19 +05:30
drawLines(x, y, commit) {
let i = 0;
let len = 0;
let arrow = '';
let offset = [];
let color = [];
2018-11-08 19:23:39 +05:30
const { r } = this;
const ref = commit.parents;
const results = [];
for (i = 0, len = ref.length; i < len; i += 1) {
2019-12-26 22:10:19 +05:30
const parent = ref[i];
const parentCommit = this.preparedCommits[parent[0]];
const parentY = this.offsetY + this.unitTime * parentCommit.time;
const parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
const parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
2017-08-17 22:00:37 +05:30
// Set line color
if (parentCommit.space <= commit.space) {
color = this.colors[commit.space];
} else {
color = this.colors[parentCommit.space];
}
// Build line shape
if (parent[1] === commit.space) {
offset = [0, 5];
2018-12-13 13:39:08 +05:30
arrow = 'l-2,5,4,0,-2,-5,0,5';
2017-08-17 22:00:37 +05:30
} else if (parent[1] < commit.space) {
offset = [3, 3];
2018-12-13 13:39:08 +05:30
arrow = 'l5,0,-2,4,-3,-4,4,2';
2017-08-17 22:00:37 +05:30
} else {
offset = [-3, 3];
2018-12-13 13:39:08 +05:30
arrow = 'l-5,0,2,4,3,-4,-4,2';
2017-08-17 22:00:37 +05:30
}
// Start point
2019-12-26 22:10:19 +05:30
const route = ['M', x + offset[0], y + offset[1]];
2017-08-17 22:00:37 +05:30
// Add arrow if not first parent
if (i > 0) {
route.push(arrow);
2016-09-13 17:45:13 +05:30
}
2017-08-17 22:00:37 +05:30
// Circumvent if overlap
if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
2018-12-13 13:39:08 +05:30
route.push('L', parentX2, y + 10, 'L', parentX2, parentY - 5);
2017-08-17 22:00:37 +05:30
}
// End point
2018-12-13 13:39:08 +05:30
route.push('L', parentX1, parentY);
results.push(
r.path(route).attr({
stroke: color,
'stroke-width': 2,
}),
);
2017-08-17 22:00:37 +05:30
}
return results;
2019-12-26 22:10:19 +05:30
}
2017-08-17 22:00:37 +05:30
2019-12-26 22:10:19 +05:30
markCommit(commit) {
2017-08-17 22:00:37 +05:30
if (commit.id === this.options.commit_id) {
2018-11-08 19:23:39 +05:30
const { r } = this;
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
const y = this.offsetY + this.unitTime * commit.time;
2018-12-13 13:39:08 +05:30
r.path(['M', x + 5, y, 'L', x + 15, y + 4, 'L', x + 15, y - 4, 'Z']).attr({
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
2017-08-17 22:00:37 +05:30
});
// Displayed in the center
return this.element.scrollTop(y - this.graphHeight / 2);
2016-09-13 17:45:13 +05:30
}
2019-12-26 22:10:19 +05:30
}
}