* move semantic.dropdown.custom.js to webpack Also disabled a annoying linter rule which insisted that imports can not contain a file extension. Fixes: https://github.com/go-gitea/gitea/issues/8971 * reorganize web_src files and rebuild * restart citags/v1.21.12.1
| @@ -1,2 +1 @@ | |||
| /public/js/semantic.dropdown.custom.js | |||
| /web_src/js/vendor/** | |||
| /web_src/js/semanticDropdown.js | |||
| @@ -25,14 +25,15 @@ globals: | |||
| Vue: false | |||
| rules: | |||
| arrow-body-style: [0] | |||
| camelcase: [0] | |||
| comma-dangle: [2, only-multiline] | |||
| consistent-return: [0] | |||
| default-case: [0] | |||
| func-names: [0] | |||
| import/extensions: [0] | |||
| max-len: [0] | |||
| newline-per-chained-call: [0] | |||
| arrow-body-style: [0] | |||
| no-alert: [0] | |||
| no-continue: [0] | |||
| no-mixed-operators: [0] | |||
| @@ -31,9 +31,9 @@ | |||
| <td><a href="https://github.com/Semantic-Org/Semantic-UI/archive/2.3.1.tar.gz">semantic-UI-2.3.1.tar.gz</a></td> | |||
| </tr> | |||
| <tr> | |||
| <td><a href="../js/semantic.dropdown.custom.js">semantic.dropdown.custom.js</a></td> | |||
| <td><a href="../js/index.js">semantic.dropdown</a></td> | |||
| <td><a href="https://semantic-ui.mit-license.org/">Expat</a></td> | |||
| <td><a href="https://github.com/go-gitea/gitea/tree/master/public/js">semantic.dropdown.custom.js</a></td> | |||
| <td><a href="https://github.com/go-gitea/gitea/tree/master/web_src/vendor/semantic.dropdown">semantic.dropdown.custom.js</a></td> | |||
| </tr> | |||
| <tr> | |||
| <td><a href="../js/index.js">index.js</a></td> | |||
| @@ -118,7 +118,6 @@ | |||
| <!-- JavaScript --> | |||
| <script src="{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.js"></script> | |||
| <script src="{{StaticUrlPrefix}}/js/semantic.dropdown.custom.js?v={{MD5 AppVer}}"></script> | |||
| <script src="{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}"></script> | |||
| {{if .EnableHeatmap}} | |||
| <script src="{{StaticUrlPrefix}}/vendor/plugins/moment/moment.min.js" charset="utf-8"></script> | |||
| @@ -7,7 +7,6 @@ var urlsToCache = [ | |||
| '{{StaticUrlPrefix}}/vendor/plugins/semantic/semantic.min.js', | |||
| '{{StaticUrlPrefix}}/js/index.js?v={{MD5 AppVer}}', | |||
| '{{StaticUrlPrefix}}/js/gitgraph.js?v={{MD5 AppVer}}', | |||
| '{{StaticUrlPrefix}}/js/semantic.dropdown.custom.js?v={{MD5 AppVer}}', | |||
| '{{StaticUrlPrefix}}/vendor/plugins/clipboard/clipboard.min.js', | |||
| '{{StaticUrlPrefix}}/vendor/plugins/vue/vue.min.js', | |||
| '{{StaticUrlPrefix}}/vendor/plugins/emojify/emojify.custom.js', | |||
| @@ -1,3 +1,5 @@ | |||
| /* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.css | |||
| Changes include the removal of `body` and `em` styles */ | |||
| #git-graph-container, #rel-container {float:left;} | |||
| #rel-container {max-width:30%; overflow-x:auto;} | |||
| #git-graph-container {overflow-x:auto; width:100%} | |||
| @@ -1,16 +1,426 @@ | |||
| $(async () => { | |||
| const graphCanvas = document.getElementById('graph-canvas'); | |||
| if (!graphCanvas) return; | |||
| /* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js | |||
| Changes include conversion to ES6 and linting fixes */ | |||
| const [{ default: gitGraph }] = await Promise.all([ | |||
| import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.js'), | |||
| import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.css'), | |||
| ]); | |||
| /* | |||
| * @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 = []; | |||
| $('#graph-raw-list li span.node-relation').each(function () { | |||
| graphList.push($(this).text()); | |||
| }); | |||
| gitGraph(graphCanvas, 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; | |||
| } | |||
| } | |||
| } | |||
| /* eslint-disable-next-line */ | |||
| if (condensePrevLength < condenseCurrentLength | |||
| && ((nodePos = findColomn('*', currentRow)) !== -1 | |||
| && (findColomn('_', currentRow) === -1))) { | |||
| flows.splice(nodePos - 1, 0, genNewFlow()); | |||
| } | |||
| /* eslint-disable-next-line */ | |||
| if (prevRowLength > currentRow.length | |||
| && (nodePos = findColomn('*', prevRow)) !== -1) { | |||
| 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 | |||
| @@ -0,0 +1,16 @@ | |||
| $(async () => { | |||
| const graphCanvas = document.getElementById('graph-canvas'); | |||
| if (!graphCanvas) return; | |||
| const [{ default: gitGraph }] = await Promise.all([ | |||
| import(/* webpackChunkName: "gitgraph" */'./gitGraph.js'), | |||
| import(/* webpackChunkName: "gitgraph" */'../css/gitGraph.css'), | |||
| ]); | |||
| const graphList = []; | |||
| $('#graph-raw-list li span.node-relation').each(function () { | |||
| graphList.push($(this).text()); | |||
| }); | |||
| gitGraph(graphCanvas, graphList); | |||
| }); | |||
| @@ -2,8 +2,9 @@ | |||
| /* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ | |||
| /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ | |||
| import './publicPath'; | |||
| import './gitGraph'; | |||
| import './publicPath.js'; | |||
| import './gitGraphLoader.js'; | |||
| import './semanticDropdown.js'; | |||
| function htmlEncode(text) { | |||
| return jQuery('<div />').text(text).html(); | |||
| @@ -1,3 +1,6 @@ | |||
| /* This is a patched version of semantic.dropdown which includes a11y changes, see | |||
| https://github.com/go-gitea/gitea/pull/8638#issuecomment-549175290 */ | |||
| /*! | |||
| * # Semantic UI 2.3.1 - Dropdown | |||
| * http://github.com/semantic-org/semantic-ui/ | |||
| @@ -1,24 +0,0 @@ | |||
| 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 fgdev 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. | |||
| @@ -1,419 +0,0 @@ | |||
| /* | |||
| * @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 | |||
| && (findColomn('_', currentRow) === -1))) { | |||
| flows.splice(nodePos - 1, 0, genNewFlow()); | |||
| } | |||
| if (prevRowLength > currentRow.length | |||
| && (nodePos = findColomn('*', prevRow)) !== -1) { | |||
| 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] === '|') { | |||
| 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)) { | |||
| 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 | |||