Browse Source

Rewrite GitGraph.js (#12137)

The current vendored gitgraph.js is no longer maintained and is
difficult to understand, fix and maintain.

This PR completely rewrites its logic - hopefully in a clearer fashion
and easier to maintain.

It also includes @silverwind's improvements of coloring the commit dots
and preventing the flash of incorrect content.

Further changes to contemplate in future will be abstracting out of the
flows to an object, storing the involved commit references on the flows
etc. However, this is probably a required step for this.

Replaces #12131
Fixes #11981 (part 3)

Signed-off-by: Andrew Thornton <art27@cantab.net>
for-closed-social
zeripath 4 years ago
committed by GitHub
parent
commit
2ab185d3ab
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 567 additions and 436 deletions
  1. +559
    -4
      web_src/js/features/gitgraph.js
  2. +0
    -432
      web_src/js/vendor/gitgraph.js
  3. +8
    -0
      web_src/less/_repository.less

+ 559
- 4
web_src/js/features/gitgraph.js View File

@ -1,13 +1,568 @@
// Although inspired by the https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js
// this has been completely rewritten with almost no remaining code
// GitGraphCanvas is a canvas for drawing gitgraphs on to
class GitGraphCanvas {
constructor(canvas, widthUnits, heightUnits, config) {
this.ctx = canvas.getContext('2d');
const width = widthUnits * config.unitSize;
this.height = heightUnits * config.unitSize;
const ratio = window.devicePixelRatio || 1;
canvas.width = width * ratio;
canvas.height = this.height * ratio;
canvas.style.width = `${width}px`;
canvas.style.height = `${this.height}px`;
this.ctx.lineWidth = config.lineWidth;
this.ctx.lineJoin = 'round';
this.ctx.lineCap = 'round';
this.ctx.scale(ratio, ratio);
this.config = config;
}
drawLine(moveX, moveY, lineX, lineY, color) {
this.ctx.strokeStyle = color;
this.ctx.beginPath();
this.ctx.moveTo(moveX, moveY);
this.ctx.lineTo(lineX, lineY);
this.ctx.stroke();
}
drawLineRight(x, y, color) {
this.drawLine(
x - 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x + 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
color
);
}
drawLineUp(x, y, color) {
this.drawLine(
x,
y + this.config.unitSize / 2,
x,
y - this.config.unitSize / 2,
color
);
}
drawNode(x, y, color) {
this.ctx.strokeStyle = color;
this.drawLineUp(x, y, color);
this.ctx.beginPath();
this.ctx.arc(x, y, this.config.nodeRadius, 0, Math.PI * 2, true);
this.ctx.fillStyle = color;
this.ctx.fill();
}
drawLineIn(x, y, color) {
this.drawLine(
x + 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x - 0.5 * this.config.unitSize,
y - this.config.unitSize / 2,
color
);
}
drawLineOut(x, y, color) {
this.drawLine(
x - 0.5 * this.config.unitSize,
y + this.config.unitSize / 2,
x + 0.5 * this.config.unitSize,
y - this.config.unitSize / 2,
color
);
}
drawSymbol(symbol, columnNumber, rowNumber, color) {
const y = this.height - this.config.unitSize * (rowNumber + 0.5);
const x = this.config.unitSize * 0.5 * (columnNumber + 1);
switch (symbol) {
case '-':
if (columnNumber % 2 === 1) {
this.drawLineRight(x, y, color);
}
break;
case '_':
this.drawLineRight(x, y, color);
break;
case '*':
this.drawNode(x, y, color);
break;
case '|':
this.drawLineUp(x, y, color);
break;
case '/':
this.drawLineOut(x, y, color);
break;
case '\\':
this.drawLineIn(x, y, color);
break;
case '.':
case ' ':
break;
default:
console.error('Unknown symbol', symbol, color);
}
}
}
class GitGraph {
constructor(canvas, rawRows, config) {
this.rows = [];
let maxWidth = 0;
for (let i = 0; i < rawRows.length; i++) {
const rowStr = rawRows[i];
maxWidth = Math.max(rowStr.replace(/([_\s.-])/g, '').length, maxWidth);
const rowArray = rowStr.split('');
this.rows.unshift(rowArray);
}
this.currentFlows = [];
this.previousFlows = [];
this.gitGraphCanvas = new GitGraphCanvas(
canvas,
maxWidth,
this.rows.length,
config
);
}
generateNewFlow(column) {
let newId;
do {
newId = generateRandomColorString();
} while (this.hasFlow(newId, column));
return {id: newId, color: `#${newId}`};
}
hasFlow(id, column) {
// We want to find the flow with the current ID
// Possible flows are those in the currentFlows
// Or flows in previousFlows[column-2:...]
for (
let idx = column - 2 < 0 ? 0 : column - 2;
idx < this.previousFlows.length;
idx++
) {
if (this.previousFlows[idx] && this.previousFlows[idx].id === id) {
return true;
}
}
for (let idx = 0; idx < this.currentFlows.length; idx++) {
if (this.currentFlows[idx] && this.currentFlows[idx].id === id) {
return true;
}
}
return false;
}
takePreviousFlow(column) {
if (column < this.previousFlows.length && this.previousFlows[column]) {
const flow = this.previousFlows[column];
this.previousFlows[column] = null;
return flow;
}
return this.generateNewFlow(column);
}
draw() {
if (this.rows.length === 0) {
return;
}
this.currentFlows = new Array(this.rows[0].length);
// Generate flows for the first row - I do not believe that this can contain '_', '-', '.'
for (let column = 0; column < this.rows[0].length; column++) {
if (this.rows[0][column] === ' ') {
continue;
}
this.currentFlows[column] = this.generateNewFlow(column);
}
// Draw the first row
for (let column = 0; column < this.rows[0].length; column++) {
const symbol = this.rows[0][column];
const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
this.gitGraphCanvas.drawSymbol(symbol, column, 0, color);
}
for (let row = 1; row < this.rows.length; row++) {
// Done previous row - step up the row
const currentRow = this.rows[row];
const previousRow = this.rows[row - 1];
this.previousFlows = this.currentFlows;
this.currentFlows = new Array(currentRow.length);
// Set flows for this row
for (let column = 0; column < currentRow.length; column++) {
column = this.setFlowFor(column, currentRow, previousRow);
}
// Draw this row
for (let column = 0; column < currentRow.length; column++) {
const symbol = currentRow[column];
const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
this.gitGraphCanvas.drawSymbol(symbol, column, row, color);
}
}
}
setFlowFor(column, currentRow, previousRow) {
const symbol = currentRow[column];
switch (symbol) {
case '|':
case '*':
return this.setUpFlow(column, currentRow, previousRow);
case '/':
return this.setOutFlow(column, currentRow, previousRow);
case '\\':
return this.setInFlow(column, currentRow, previousRow);
case '_':
return this.setRightFlow(column, currentRow, previousRow);
case '-':
return this.setLeftFlow(column, currentRow, previousRow);
case ' ':
// In space no one can hear you flow ... (?)
return column;
default:
// Unexpected so let's generate a new flow and wait for bug-reports
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
}
// setUpFlow handles '|' or '*' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setUpFlow(column, currentRow, previousRow) {
// If ' |/' or ' |_'
// '/|' '/|' -> Take the '|' flow directly beneath us
if (
column + 1 < currentRow.length &&
(currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
column < previousRow.length &&
(previousRow[column] === '|' || previousRow[column] === '*') &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If ' |/' or ' |_'
// '/ ' '/ ' -> Take the '/' flow from the preceding column
if (
column + 1 < currentRow.length &&
(currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If ' |'
// '/' -> Take the '/' flow - (we always prefer the left-most flow)
if (
column > 0 &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If '|' OR '|' take the '|' flow
// '|' '*'
if (
column < previousRow.length &&
(previousRow[column] === '|' || previousRow[column] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If '| ' keep the '\' flow
// ' \'
if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
return column;
}
// Otherwise just create a new flow - probably this is an error...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setOutFlow handles '/' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setOutFlow(column, currentRow, previousRow) {
// If '_/' -> keep the '_' flow
if (column > 0 && currentRow[column - 1] === '_') {
this.currentFlows[column] = this.currentFlows[column - 1];
return column;
}
// If '_|/' -> keep the '_' flow
if (
column > 1 &&
(currentRow[column - 1] === '|' || currentRow[column - 1] === '*') &&
currentRow[column - 2] === '_'
) {
this.currentFlows[column] = this.currentFlows[column - 2];
return column;
}
// If '|/'
// '/' -> take the '/' flow (if it is still available)
if (
column > 1 &&
currentRow[column - 1] === '|' &&
column - 2 < previousRow.length &&
previousRow[column - 2] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 2);
return column;
}
// If ' /'
// '/' -> take the '/' flow, but transform the symbol to '|' due to our spacing
// This should only happen if there are 3 '/' - in a row so we don't need to be cleverer here
if (
column > 0 &&
currentRow[column - 1] === ' ' &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
currentRow[column] = '|';
return column;
}
// If ' /'
// '|' -> take the '|' flow
if (
column > 0 &&
currentRow[column - 1] === ' ' &&
column - 1 < previousRow.length &&
(previousRow[column - 1] === '|' || previousRow[column - 1] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// If '/' <- Not sure this ever happens... but take the '\' flow
// '\'
if (column < previousRow.length && previousRow[column] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// Otherwise just generate a new flow and wait for bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setInFlow handles '\' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setInFlow(column, currentRow, previousRow) {
// If '\?'
// '/?' -> take the '/' flow
if (column < previousRow.length && previousRow[column] === '/') {
this.currentFlows[column] = this.takePreviousFlow(column);
return column;
}
// If '\?'
// ' \' -> take the '\' flow and reassign to '|'
// This should only happen if there are 3 '\' - in a row so we don't need to be cleverer here
if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
currentRow[column] = '|';
return column;
}
// If '\?'
// ' |' -> take the '|' flow
if (
column + 1 < previousRow.length &&
(previousRow[column + 1] === '|' || previousRow[column + 1] === '*')
) {
this.currentFlows[column] = this.takePreviousFlow(column + 1);
return column;
}
// Otherwise just generate a new flow and wait for bug-reports if we're wrong...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setRightFlow handles '_' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row
setRightFlow(column, currentRow, previousRow) {
// if '__' keep the '_' flow
if (column > 0 && currentRow[column - 1] === '_') {
this.currentFlows[column] = this.currentFlows[column - 1];
return column;
}
// if '_|_' -> keep the '_' flow
if (
column > 1 &&
currentRow[column - 1] === '|' &&
currentRow[column - 2] === '_'
) {
this.currentFlows[column] = this.currentFlows[column - 2];
return column;
}
// if ' _' -> take the '/' flow
// '/ '
if (
column > 0 &&
column - 1 < previousRow.length &&
previousRow[column - 1] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 1);
return column;
}
// if ' |_'
// '/? ' -> take the '/' flow (this may cause generation...)
// we can do this because we know that git graph
// doesn't create compact graphs like: ' |_'
// '//'
if (
column > 1 &&
column - 2 < previousRow.length &&
previousRow[column - 2] === '/'
) {
this.currentFlows[column] = this.takePreviousFlow(column - 2);
return column;
}
// There really shouldn't be another way of doing this - generate and wait for bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
return column;
}
// setLeftFlow handles '----.' - returns the last column that was set
// generally we prefer to take the left most flow from the previous row that terminates this left recursion
setLeftFlow(column, currentRow, previousRow) {
// This is: '----------.' or the like
// ' \ \ /|\'
// Find the end of the '-' or nearest '/|\' in the previousRow :
let originalColumn = column;
let flow;
for (; column < currentRow.length && currentRow[column] === '-'; column++) {
if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
flow = this.takePreviousFlow(column - 1);
break;
} else if (column < previousRow.length && previousRow[column] === '|') {
flow = this.takePreviousFlow(column);
break;
} else if (
column + 1 < previousRow.length &&
previousRow[column + 1] === '\\'
) {
flow = this.takePreviousFlow(column + 1);
break;
}
}
// if we have a flow then we found a '/|\' in the previousRow
if (flow) {
for (; originalColumn < column + 1; originalColumn++) {
this.currentFlows[originalColumn] = flow;
}
return column;
}
// If the symbol in the column is not a '.' then there's likely an error
if (currentRow[column] !== '.') {
// It really should end in a '.' but this one doesn't...
// 1. Step back - we don't want to eat this column
column--;
// 2. Generate a new flow and await bug-reports...
this.currentFlows[column] = this.generateNewFlow(column);
// 3. Assign all of the '-' to the same flow.
for (; originalColumn < column; originalColumn++) {
this.currentFlows[originalColumn] = this.currentFlows[column];
}
return column;
}
// We have a terminal '.' eg. the current row looks like '----.'
// the previous row should look like one of '/|\' eg. ' \'
if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
flow = this.takePreviousFlow(column - 1);
} else if (column < previousRow.length && previousRow[column] === '|') {
flow = this.takePreviousFlow(column);
} else if (
column + 1 < previousRow.length &&
previousRow[column + 1] === '\\'
) {
flow = this.takePreviousFlow(column + 1);
} else {
// Again unexpected so let's generate and wait the bug-report
flow = this.generateNewFlow(column);
}
// Assign all of the rest of the ----. to this flow.
for (; originalColumn < column + 1; originalColumn++) {
this.currentFlows[originalColumn] = flow;
}
return column;
}
}
function generateRandomColorString() {
const chars = '0123456789ABCDEF';
const stringLength = 6;
let randomString = '',
rnum,
i;
for (i = 0; i < stringLength; i++) {
rnum = Math.floor(Math.random() * chars.length);
randomString += chars.substring(rnum, rnum + 1);
}
return randomString;
}
export default async function initGitGraph() { export default async function initGitGraph() {
const graphCanvas = document.getElementById('graph-canvas'); const graphCanvas = document.getElementById('graph-canvas');
if (!graphCanvas) return;
const {default: gitGraph} = await import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js');
if (!graphCanvas || !graphCanvas.getContext) return;
// Grab the raw graphList
const graphList = []; const graphList = [];
$('#graph-raw-list li span.node-relation').each(function () { $('#graph-raw-list li span.node-relation').each(function () {
graphList.push($(this).text()); graphList.push($(this).text());
}); });
gitGraph(graphCanvas, graphList);
// Define some drawing parameters
const config = {
unitSize: 20,
lineWidth: 3,
nodeRadius: 4
};
const gitGraph = new GitGraph(graphCanvas, graphList, config);
gitGraph.draw();
graphCanvas.closest('#git-graph-container').classList.add('in');
} }

+ 0
- 432
web_src/js/vendor/gitgraph.js View File

@ -1,432 +0,0 @@
/* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js
Changes include conversion to ES6 and linting fixes */
/*
* @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD 3-Clause
* Copyright (c) 2011, Terrence Lee <kill889@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the <organization> nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
export default function gitGraph(canvas, rawGraphList, config) {
if (!canvas.getContext) {
return;
}
if (typeof config === 'undefined') {
config = {
unitSize: 20,
lineWidth: 3,
nodeRadius: 4
};
}
const flows = [];
const graphList = [];
const ctx = canvas.getContext('2d');
const devicePixelRatio = window.devicePixelRatio || 1;
const backingStoreRatio = ctx.webkitBackingStorePixelRatio
|| ctx.mozBackingStorePixelRatio
|| ctx.msBackingStorePixelRatio
|| ctx.oBackingStorePixelRatio
|| ctx.backingStorePixelRatio || 1;
const ratio = devicePixelRatio / backingStoreRatio;
const init = function () {
let maxWidth = 0;
let i;
const l = rawGraphList.length;
let row;
let midStr;
for (i = 0; i < l; i++) {
midStr = rawGraphList[i].replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
midStr = midStr.replace(/(--)|(-\.)/g,'-')
maxWidth = Math.max(midStr.replace(/(_|\s)/g, '').length, maxWidth);
row = midStr.split('');
graphList.unshift(row);
}
const width = maxWidth * config.unitSize;
const height = graphList.length * config.unitSize;
canvas.width = width * ratio;
canvas.height = height * ratio;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.lineWidth = config.lineWidth;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.scale(ratio, ratio);
};
const genRandomStr = function () {
const chars = '0123456789ABCDEF';
const stringLength = 6;
let randomString = '', rnum, i;
for (i = 0; i < stringLength; i++) {
rnum = Math.floor(Math.random() * chars.length);
randomString += chars.substring(rnum, rnum + 1);
}
return randomString;
};
const findFlow = function (id) {
let i = flows.length;
while (i-- && flows[i].id !== id);
return i;
};
const findColomn = function (symbol, row) {
let i = row.length;
while (i-- && row[i] !== symbol);
return i;
};
const findBranchOut = function (row) {
if (!row) {
return -1;
}
let i = row.length;
while (i--
&& !(row[i - 1] && row[i] === '/' && row[i - 1] === '|')
&& !(row[i - 2] && row[i] === '_' && row[i - 2] === '|'));
return i;
};
const findLineBreak = function (row) {
if (!row) {
return -1;
}
let i = row.length;
while (i--
&& !(row[i - 1] && row[i - 2] && row[i] === ' ' && row[i - 1] === '|' && row[i - 2] === '_'));
return i;
};
const genNewFlow = function () {
let newId;
do {
newId = genRandomStr();
} while (findFlow(newId) !== -1);
return { id: newId, color: `#${newId}` };
};
// Draw methods
const drawLine = function (moveX, moveY, lineX, lineY, color) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.moveTo(moveX, moveY);
ctx.lineTo(lineX, lineY);
ctx.stroke();
};
const drawLineRight = function (x, y, color) {
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y + config.unitSize / 2, color);
};
const drawLineUp = function (x, y, color) {
drawLine(x, y + config.unitSize / 2, x, y - config.unitSize / 2, color);
};
const drawNode = function (x, y, color) {
ctx.strokeStyle = color;
drawLineUp(x, y, color);
ctx.beginPath();
ctx.arc(x, y, config.nodeRadius, 0, Math.PI * 2, true);
ctx.fill();
};
const drawLineIn = function (x, y, color) {
drawLine(x + config.unitSize, y + config.unitSize / 2, x, y - config.unitSize / 2, color);
};
const drawLineOut = function (x, y, color) {
drawLine(x, y + config.unitSize / 2, x + config.unitSize, y - config.unitSize / 2, color);
};
const draw = function (graphList) {
let colomn, colomnIndex, prevColomn, condenseIndex, breakIndex = -1;
let x, y;
let color;
let nodePos;
let tempFlow;
let prevRowLength = 0;
let flowSwapPos = -1;
let lastLinePos;
let i, l;
let condenseCurrentLength, condensePrevLength = 0;
let inlineIntersect = false;
// initiate color array for first row
for (i = 0, l = graphList[0].length; i < l; i++) {
if (graphList[0][i] !== '_' && graphList[0][i] !== ' ') {
flows.push(genNewFlow());
}
}
y = (canvas.height / ratio) - config.unitSize * 0.5;
// iterate
for (i = 0, l = graphList.length; i < l; i++) {
x = config.unitSize * 0.5;
const currentRow = graphList[i];
const nextRow = graphList[i + 1];
const prevRow = graphList[i - 1];
flowSwapPos = -1;
condenseCurrentLength = currentRow.filter((val) => {
return (val !== ' ' && val !== '_');
}).length;
// pre process begin
// use last row for analysing
if (prevRow) {
if (!inlineIntersect) {
// intersect might happen
for (colomnIndex = 0; colomnIndex < prevRowLength; colomnIndex++) {
if (prevRow[colomnIndex + 1]
&& (prevRow[colomnIndex] === '/' && prevRow[colomnIndex + 1] === '|')
|| ((prevRow[colomnIndex] === '_' && prevRow[colomnIndex + 1] === '|')
&& (prevRow[colomnIndex + 2] === '/'))) {
flowSwapPos = colomnIndex;
// swap two flow
tempFlow = { id: flows[flowSwapPos].id, color: flows[flowSwapPos].color };
flows[flowSwapPos].id = flows[flowSwapPos + 1].id;
flows[flowSwapPos].color = flows[flowSwapPos + 1].color;
flows[flowSwapPos + 1].id = tempFlow.id;
flows[flowSwapPos + 1].color = tempFlow.color;
}
}
}
if (condensePrevLength < condenseCurrentLength
&& ((nodePos = findColomn('*', currentRow)) !== -1 // eslint-disable-line no-cond-assign
&& (findColomn('_', currentRow) === -1))) {
flows.splice(nodePos - 1, 0, genNewFlow());
}
if (prevRowLength > currentRow.length
&& (nodePos = findColomn('*', prevRow)) !== -1) { // eslint-disable-line no-cond-assign
if (findColomn('_', currentRow) === -1
&& findColomn('/', currentRow) === -1
&& findColomn('\\', currentRow) === -1) {
flows.splice(nodePos + 1, 1);
}
}
} // done with the previous row
prevRowLength = currentRow.length; // store for next round
colomnIndex = 0; // reset index
condenseIndex = 0;
condensePrevLength = 0;
breakIndex = -1; // reset break index
while (colomnIndex < currentRow.length) {
colomn = currentRow[colomnIndex];
if (colomn !== ' ' && colomn !== '_') {
++condensePrevLength;
}
// check and fix line break in next row
if (colomn === '/' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '|') {
/* eslint-disable-next-line */
if ((breakIndex = findLineBreak(nextRow)) !== -1) {
nextRow.splice(breakIndex, 1);
}
}
// if line break found replace all '/' with '|' after breakIndex in previous row
if (breakIndex !== -1 && colomn === '/' && colomnIndex > breakIndex) {
currentRow[colomnIndex] = '|';
colomn = '|';
}
if (colomn === ' '
&& currentRow[colomnIndex + 1]
&& currentRow[colomnIndex + 1] === '_'
&& currentRow[colomnIndex - 1]
&& currentRow[colomnIndex - 1] === '|') {
currentRow.splice(colomnIndex, 1);
currentRow[colomnIndex] = '/';
colomn = '/';
}
// create new flow only when no intersect happened
if (flowSwapPos === -1
&& colomn === '/'
&& currentRow[colomnIndex - 1]
&& currentRow[colomnIndex - 1] === '|') {
flows.splice(condenseIndex, 0, genNewFlow());
}
// change \ and / to | when it's in the last position of the whole row
if (colomn === '/' || colomn === '\\') {
if (!(colomn === '/' && findBranchOut(nextRow) === -1)) {
/* eslint-disable-next-line */
if ((lastLinePos = Math.max(findColomn('|', currentRow),
findColomn('*', currentRow))) !== -1
&& (lastLinePos < colomnIndex - 1)) {
while (currentRow[++lastLinePos] === ' ');
if (lastLinePos === colomnIndex) {
currentRow[colomnIndex] = '|';
}
}
}
}
if (colomn === '*'
&& prevRow
&& prevRow[condenseIndex + 1] === '\\') {
flows.splice(condenseIndex + 1, 1);
}
if (colomn !== ' ') {
++condenseIndex;
}
++colomnIndex;
}
condenseCurrentLength = currentRow.filter((val) => {
return (val !== ' ' && val !== '_');
}).length;
colomnIndex = 0;
// a little inline analysis and draw process
while (colomnIndex < currentRow.length) {
colomn = currentRow[colomnIndex];
prevColomn = currentRow[colomnIndex - 1];
if (currentRow[colomnIndex] === ' ') {
currentRow.splice(colomnIndex, 1);
x += config.unitSize;
continue;
}
// inline intersect
if ((colomn === '_' || colomn === '/')
&& currentRow[colomnIndex - 1] === '|'
&& currentRow[colomnIndex - 2] === '_') {
inlineIntersect = true;
tempFlow = flows.splice(colomnIndex - 2, 1)[0];
flows.splice(colomnIndex - 1, 0, tempFlow);
currentRow.splice(colomnIndex - 2, 1);
colomnIndex -= 1;
} else {
inlineIntersect = false;
}
if (colomn === '|' && currentRow[colomnIndex - 1] && currentRow[colomnIndex - 1] === '\\') {
flows.splice(colomnIndex, 0, genNewFlow());
}
color = flows[colomnIndex].color;
switch (colomn) {
case '-':
case '_':
drawLineRight(x, y, color);
x += config.unitSize;
break;
case '*':
drawNode(x, y, color);
break;
case '|':
if (prevColomn && prevColomn === '\\') {
x += config.unitSize;
}
drawLineUp(x, y, color);
break;
case '/':
if (prevColomn
&& (prevColomn === '/'
|| prevColomn === ' ')) {
x -= config.unitSize;
}
drawLineOut(x, y, color);
x += config.unitSize;
break;
case '\\':
drawLineIn(x, y, color);
break;
}
++colomnIndex;
}
y -= config.unitSize;
}
// do some clean up
if (flows.length > condenseCurrentLength) {
flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength);
}
};
init();
draw(graphList);
}
// @end-license

+ 8
- 0
web_src/less/_repository.less View File

@ -3078,3 +3078,11 @@ tbody.commit-list {
} }
} }
} }
#git-graph-container {
display: none;
}
#git-graph-container.in {
display: block;
}

Loading…
Cancel
Save