UNPKG

dependency-cruiser

Version:

Validate and visualize dependencies. With your rules. JavaScript, TypeScript, CoffeeScript. ES6, CommonJS, AMD.

285 lines (249 loc) 6.98 kB
var gMode = new Mode(); var title2ElementMap = (function makeElementMap() { /** @type {NodeListOf<SVGGElement>} */ var nodes = document.querySelectorAll(".node"); /** @type {NodeListOf<SVGGElement>} */ var edges = document.querySelectorAll(".edge"); return new Title2ElementMap(edges, nodes); })(); function getHoverHandler(pTitle2ElementMap) { /** @type {string} */ var currentHighlightedTitle = ""; /** @param {MouseEvent} pMouseEvent */ return function hoverHighlightHandler(pMouseEvent) { var closestNodeOrEdge = pMouseEvent.target.closest(".edge, .node"); var closestTitleText = getTitleText(closestNodeOrEdge); if ( currentHighlightedTitle !== closestTitleText && gMode.get() === gMode.HOVER ) { resetNodesAndEdges(); addHighlight(closestNodeOrEdge); pTitle2ElementMap.get(closestTitleText).forEach(addHighlight); currentHighlightedTitle = closestTitleText; } }; } function getSelectHandler(pTitle2ElementMap) { /** @type {string} */ var currentHighlightedTitle = ""; /** @param {MouseEvent} pMouseEvent */ return function selectHighlightHandler(pMouseEvent) { pMouseEvent.preventDefault(); var closestNodeOrEdge = pMouseEvent.target.closest(".edge, .node"); var closestTitleText = getTitleText(closestNodeOrEdge); if (closestNodeOrEdge) { gMode.setToSelect(); } else { gMode.setToHover(); } if (currentHighlightedTitle !== closestTitleText) { resetNodesAndEdges(); addHighlight(closestNodeOrEdge); pTitle2ElementMap.get(closestTitleText).forEach(addHighlight); currentHighlightedTitle = closestTitleText; } }; } function Mode() { var HOVER = 1; var SELECT = 2; function setToHover() { this._mode = HOVER; } function setToSelect() { this._mode = SELECT; } /** * @returns {number} */ function get() { return this._mode || HOVER; } return { HOVER: HOVER, SELECT: SELECT, setToHover: setToHover, setToSelect: setToSelect, get: get, }; } /** * * @param {SVGGelement[]} pEdges * @param {SVGGElement[]} pNodes * @return {{get: (pTitleText:string) => SVGGElement[]}} */ function Title2ElementMap(pEdges, pNodes) { /* {{[key: string]: SVGGElement[]}} */ var elementMap = buildMap(pEdges, pNodes); /** * @param {NodeListOf<SVGGElement>} pEdges * @param {NodeListOf<SVGGElement>} pNodes * @return {{[key: string]: SVGGElement[]}} */ function buildMap(pEdges, pNodes) { var title2NodeMap = buildTitle2NodeMap(pNodes); return nodeListToArray(pEdges).reduce(addEdgeToMap(title2NodeMap), {}); } /** * @param {NodeListOf<SVGGElement>} pNodes * @return {{[key: string]: SVGGElement}} */ function buildTitle2NodeMap(pNodes) { return nodeListToArray(pNodes).reduce(addNodeToMap, {}); } function addNodeToMap(pMap, pNode) { var titleText = getTitleText(pNode); if (titleText) { pMap[titleText] = pNode; } return pMap; } function addEdgeToMap(pNodeMap) { return function (pEdgeMap, pEdge) { /** @type {string} */ var titleText = getTitleText(pEdge); if (titleText) { var edge = pryEdgeFromTitle(titleText); pEdgeMap[titleText] = [pNodeMap[edge.from], pNodeMap[edge.to]]; (pEdgeMap[edge.from] || (pEdgeMap[edge.from] = [])).push(pEdge); (pEdgeMap[edge.to] || (pEdgeMap[edge.to] = [])).push(pEdge); } return pEdgeMap; }; } /** * * @param {string} pString * @return {{from?: string; to?:string;}} */ function pryEdgeFromTitle(pString) { var nodeNames = pString.split(/\s*->\s*/); return { from: nodeNames.shift(), to: nodeNames.shift(), }; } /** * * @param {string} pTitleText * @return {SVGGElement[]} */ function get(pTitleText) { return (pTitleText && elementMap[pTitleText]) || []; } return { get: get, }; } /** * @param {SVGGElement} pGElement * @return {string?} */ function getTitleText(pGElement) { /** @type {SVGTitleElement} */ var title = pGElement && pGElement.querySelector("title"); /** @type {string} */ var titleText = title && title.textContent; if (titleText) { titleText = titleText.trim(); } return titleText; } /** * @param {NodeListOf<Element>} pNodeList * @return {Element[]} */ function nodeListToArray(pNodeList) { var lReturnValue = []; pNodeList.forEach(function (pElement) { lReturnValue.push(pElement); }); return lReturnValue; } function resetNodesAndEdges() { nodeListToArray(document.querySelectorAll(".current")).forEach( removeHighlight, ); } /** * @param {SVGGElement} pGElement */ function removeHighlight(pGElement) { if (pGElement && pGElement.classList) { pGElement.classList.remove("current"); } } /** * @param {SVGGElement} pGroup */ function addHighlight(pGroup) { if (pGroup && pGroup.classList) { pGroup.classList.add("current"); } } var gHints = { HIDDEN: 1, SHOWN: 2, state: 1, // === HIDDEN show: function () { document.getElementById("hints").removeAttribute("style"); gHints.state = gHints.SHOWN; }, hide: function () { document.getElementById("hints").style = "display:none"; gHints.state = gHints.HIDDEN; }, toggle: function () { if ((gHints.state || gHints.HIDDEN) === gHints.HIDDEN) { gHints.show(); } else { gHints.hide(); } }, }; /** @param {KeyboardEvent} pKeyboardEvent */ function keyboardEventHandler(pKeyboardEvent) { if (pKeyboardEvent.key === "Escape") { resetNodesAndEdges(); gMode.setToHover(); gHints.hide(); } if (pKeyboardEvent.key === "F1") { pKeyboardEvent.preventDefault(); gHints.toggle(); } } document.addEventListener("contextmenu", getSelectHandler(title2ElementMap)); document.addEventListener("mouseover", getHoverHandler(title2ElementMap)); document.addEventListener("keydown", keyboardEventHandler); document.getElementById("close-hints").addEventListener("click", gHints.hide); document.getElementById("button_help").addEventListener("click", gHints.toggle); document.querySelector("svg").insertAdjacentHTML( "afterbegin", `<linearGradient id="edgeGradient"> <stop offset="0%" stop-color="fuchsia"/> <stop offset="100%" stop-color="purple"/> </linearGradient> `, ); // Slightly nudge the last numeric path value (+0.001) so SVG stroke // gradients render correctly on horizontal paths. Without them, // they don't render at all (firefox, chrome). function skewLineABit(lDrawingInstructions) { var lLastValue = lDrawingInstructions.match(/(\d+\.?\d*)$/)[0]; // Smaller values than .001 _should_ work, but don't always. // Even this value is so small that it is not visible to the // human eye (tested with the two I have at my disposal). var lIncrement = 0.001; var lNewLastValue = Number.parseFloat(lLastValue) + lIncrement; return lDrawingInstructions.replace(lLastValue, lNewLastValue); } nodeListToArray(document.querySelectorAll("path")) .filter(function (pElement) { return pElement.parentElement.classList.contains("edge"); }) .forEach(function (pElement) { pElement.attributes.d.value = skewLineABit(pElement.attributes.d.value); });