* 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 cifor-closed-social
@ -1,2 +1 @@ | |||||
/public/js/semantic.dropdown.custom.js | |||||
/web_src/js/vendor/** | |||||
/web_src/js/semanticDropdown.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;} | #git-graph-container, #rel-container {float:left;} | ||||
#rel-container {max-width:30%; overflow-x:auto;} | #rel-container {max-width:30%; overflow-x:auto;} | ||||
#git-graph-container {overflow-x:auto; width:100%} | #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 = []; | 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); | |||||
}); |
@ -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 | * # Semantic UI 2.3.1 - Dropdown | ||||
* http://github.com/semantic-org/semantic-ui/ | * 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 |