mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-30 19:08:37 +00:00 
			
		
		
		
	This is the result of `make npm-update`. I also fixed a minor linting issue which came up with the eslint 6.7 update.
		
			
				
	
	
		
			425 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			425 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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, '');
 | |
| 
 | |
|       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;
 | |
| 
 | |
|       // do some clean up
 | |
|       if (flows.length > condenseCurrentLength) {
 | |
|         flows.splice(condenseCurrentLength, flows.length - condenseCurrentLength);
 | |
|       }
 | |
| 
 | |
|       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 interset
 | |
|         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;
 | |
|         }
 | |
| 
 | |
|         color = flows[colomnIndex].color;
 | |
| 
 | |
|         switch (colomn) {
 | |
|           case '_':
 | |
|             drawLineRight(x, y, color);
 | |
| 
 | |
|             x += config.unitSize;
 | |
|             break;
 | |
| 
 | |
|           case '*':
 | |
|             drawNode(x, y, color);
 | |
|             break;
 | |
| 
 | |
|           case '|':
 | |
|             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;
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   init();
 | |
|   draw(graphList);
 | |
| }
 | |
| // @end-license
 |