mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Render the git graph on the server (#12333)
Rendering the git graph on the server means that we can properly track flows and switch from the Canvas implementation to a SVG implementation. * This implementation provides a 16 limited color selection * The uniqued color numbers are also provided * And there is also a monochrome version *In addition is a hover highlight that allows users to highlight commits on the same flow. Closes #12209 Signed-off-by: Andrew Thornton art27@cantab.net Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
		| @@ -1,568 +1,81 @@ | ||||
| // 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() { | ||||
|   const graphCanvas = document.getElementById('graph-canvas'); | ||||
|   if (!graphCanvas || !graphCanvas.getContext) return; | ||||
|   const graphContainer = document.getElementById('git-graph-container'); | ||||
|   if (!graphContainer) return; | ||||
|  | ||||
|   // Grab the raw graphList | ||||
|   const graphList = []; | ||||
|   $('#graph-raw-list li span.node-relation').each(function () { | ||||
|     graphList.push($(this).text()); | ||||
|   $('#flow-color-monochrome').on('click', () => { | ||||
|     $('#flow-color-monochrome').addClass('active'); | ||||
|     $('#flow-color-colored').removeClass('active'); | ||||
|     $('#git-graph-container').removeClass('colored').addClass('monochrome'); | ||||
|     const params = new URLSearchParams(window.location.search); | ||||
|     params.set('mode', 'monochrome'); | ||||
|     const queryString = params.toString(); | ||||
|     if (queryString) { | ||||
|       window.history.replaceState({}, '', `?${queryString}`); | ||||
|     } else { | ||||
|       window.history.replaceState({}, '', window.location.pathname); | ||||
|     } | ||||
|     $('.pagination a').each((_, that) => { | ||||
|       const href = $(that).attr('href'); | ||||
|       if (!href) return; | ||||
|       const url = new URL(href, window.location); | ||||
|       const params = url.searchParams; | ||||
|       params.set('mode', 'monochrome'); | ||||
|       url.search = `?${params.toString()}`; | ||||
|       $(that).attr('href', url.href); | ||||
|     }); | ||||
|   }); | ||||
|   $('#flow-color-colored').on('click', () => { | ||||
|     $('#flow-color-colored').addClass('active'); | ||||
|     $('#flow-color-monochrome').removeClass('active'); | ||||
|     $('#git-graph-container').addClass('colored').removeClass('monochrome'); | ||||
|     $('.pagination a').each((_, that) => { | ||||
|       const href = $(that).attr('href'); | ||||
|       if (!href) return; | ||||
|       const url = new URL(href, window.location); | ||||
|       const params = url.searchParams; | ||||
|       params.delete('mode'); | ||||
|       url.search = `?${params.toString()}`; | ||||
|       $(that).attr('href', url.href); | ||||
|     }); | ||||
|     const params = new URLSearchParams(window.location.search); | ||||
|     params.delete('mode'); | ||||
|     const queryString = params.toString(); | ||||
|     if (queryString) { | ||||
|       window.history.replaceState({}, '', `?${queryString}`); | ||||
|     } else { | ||||
|       window.history.replaceState({}, '', window.location.pathname); | ||||
|     } | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseenter', '#rev-list li', (e) => { | ||||
|     const flow = $(e.currentTarget).data('flow'); | ||||
|     if (flow === 0) return; | ||||
|     $(`#flow-${flow}`).addClass('highlight'); | ||||
|     $(e.currentTarget).addClass('hover'); | ||||
|     $(`#rev-list li[data-flow='${flow}']`).addClass('highlight'); | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseleave', '#rev-list li', (e) => { | ||||
|     const flow = $(e.currentTarget).data('flow'); | ||||
|     if (flow === 0) return; | ||||
|     $(`#flow-${flow}`).removeClass('highlight'); | ||||
|     $(e.currentTarget).removeClass('hover'); | ||||
|     $(`#rev-list li[data-flow='${flow}']`).removeClass('highlight'); | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseenter', '#rel-container .flow-group', (e) => { | ||||
|     $(e.currentTarget).addClass('highlight'); | ||||
|     const flow = $(e.currentTarget).data('flow'); | ||||
|     $(`#rev-list li[data-flow='${flow}']`).addClass('highlight'); | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseleave', '#rel-container .flow-group', (e) => { | ||||
|     $(e.currentTarget).removeClass('highlight'); | ||||
|     const flow = $(e.currentTarget).data('flow'); | ||||
|     $(`#rev-list li[data-flow='${flow}']`).removeClass('highlight'); | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseenter', '#rel-container .flow-commit', (e) => { | ||||
|     const rev = $(e.currentTarget).data('rev'); | ||||
|     $(`#rev-list li#commit-${rev}`).addClass('hover'); | ||||
|   }); | ||||
|   $('#git-graph-container').on('mouseleave', '#rel-container .flow-commit', (e) => { | ||||
|     const rev = $(e.currentTarget).data('rev'); | ||||
|     $(`#rev-list li#commit-${rev}`).removeClass('hover'); | ||||
|   }); | ||||
|  | ||||
|   // 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'); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user