diff options
Diffstat (limited to 'web_src')
-rw-r--r-- | web_src/js/draw.js | 15 | ||||
-rw-r--r-- | web_src/js/gitGraph.js | 16 | ||||
-rw-r--r-- | web_src/js/index.js | 3 | ||||
-rw-r--r-- | web_src/js/publicPath.js | 12 | ||||
-rw-r--r-- | web_src/vendor/gitgraph.js/LICENSE | 24 | ||||
-rw-r--r-- | web_src/vendor/gitgraph.js/gitgraph.custom.css | 13 | ||||
-rw-r--r-- | web_src/vendor/gitgraph.js/gitgraph.custom.js | 419 |
7 files changed, 487 insertions, 15 deletions
diff --git a/web_src/js/draw.js b/web_src/js/draw.js deleted file mode 100644 index bb9c7f28c7..0000000000 --- a/web_src/js/draw.js +++ /dev/null @@ -1,15 +0,0 @@ -/* globals gitGraph */ - -$(() => { - const graphList = []; - - if (!document.getElementById('graph-canvas')) { - return; - } - - $('#graph-raw-list li span.node-relation').each(function () { - graphList.push($(this).text()); - }); - - gitGraph(document.getElementById('graph-canvas'), graphList); -}); diff --git a/web_src/js/gitGraph.js b/web_src/js/gitGraph.js new file mode 100644 index 0000000000..cfa466d8c8 --- /dev/null +++ b/web_src/js/gitGraph.js @@ -0,0 +1,16 @@ +$(async () => { + const graphCanvas = document.getElementById('graph-canvas'); + if (!graphCanvas) return; + + const [{ default: gitGraph }] = await Promise.all([ + import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.js'), + import(/* webpackChunkName: "gitgraph" */'../vendor/gitgraph.js/gitgraph.custom.css'), + ]); + + const graphList = []; + $('#graph-raw-list li span.node-relation').each(function () { + graphList.push($(this).text()); + }); + + gitGraph(graphCanvas, graphList); +}); diff --git a/web_src/js/index.js b/web_src/js/index.js index 704647d9c4..671c66f689 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -2,6 +2,9 @@ /* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */ /* exported toggleDeadlineForm, setDeadline, updateDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */ +import './publicPath'; +import './gitGraph'; + function htmlEncode(text) { return jQuery('<div />').text(text).html(); } diff --git a/web_src/js/publicPath.js b/web_src/js/publicPath.js new file mode 100644 index 0000000000..5d277e442a --- /dev/null +++ b/web_src/js/publicPath.js @@ -0,0 +1,12 @@ +/* This sets up webpack's chunk loading to load resources from the same + directory where it loaded index.js from. This file must be imported + before any lazy-loading is being attempted. */ + +if (document.currentScript && document.currentScript.src) { + const url = new URL(document.currentScript.src); + __webpack_public_path__ = `${url.pathname.replace(/\/[^/]*$/, '')}/`; +} else { + // compat: IE11 + const script = document.querySelector('script[src*="/index.js"]'); + __webpack_public_path__ = `${script.getAttribute('src').replace(/\/[^/]*$/, '')}/`; +} diff --git a/web_src/vendor/gitgraph.js/LICENSE b/web_src/vendor/gitgraph.js/LICENSE new file mode 100644 index 0000000000..30cd6b7692 --- /dev/null +++ b/web_src/vendor/gitgraph.js/LICENSE @@ -0,0 +1,24 @@ +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.
\ No newline at end of file diff --git a/web_src/vendor/gitgraph.js/gitgraph.custom.css b/web_src/vendor/gitgraph.js/gitgraph.custom.css new file mode 100644 index 0000000000..92d2dcc20d --- /dev/null +++ b/web_src/vendor/gitgraph.js/gitgraph.custom.css @@ -0,0 +1,13 @@ +#git-graph-container, #rel-container {float:left;} +#rel-container {max-width:30%; overflow-x:auto;} +#git-graph-container {overflow-x:auto; width:100%} +#git-graph-container li {list-style-type:none;height:20px;line-height:20px; white-space:nowrap;} +#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;} +#git-graph-container li .author {color:#666666;} +#git-graph-container li .time {color:#999999;font-size:80%} +#git-graph-container li a {color:#000000;} +#git-graph-container li a:hover {text-decoration:underline;} +#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;} +#rev-container {width:100%} +#rev-list {margin:0;padding:0 5px 0 5px;min-width:95%} +#graph-raw-list {margin:0px;} diff --git a/web_src/vendor/gitgraph.js/gitgraph.custom.js b/web_src/vendor/gitgraph.js/gitgraph.custom.js new file mode 100644 index 0000000000..2c18a2d03e --- /dev/null +++ b/web_src/vendor/gitgraph.js/gitgraph.custom.js @@ -0,0 +1,419 @@ +/* + * @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 |