UNPKG

@cloudcannon/suite

Version:

A suite of gulp tools to manage static sites on CloudCannon

509 lines (478 loc) 19.8 kB
<!DOCTYPE html> <head> <title>State</title> <style> body { font-family: sans-serif; background-color: #222; margin: 8px 0; } #top-bar { width: 100%; text-align: center; border-bottom: 1px solid black; display: flex; background-color: #eee; color: #444; } #top-bar div { flex: 1; } .top-bar-options { display: flex; align-items: center; border-left: 1px solid black; } .top-bar-options > div { padding: 0px 5px; } #toggle-button h1 { font-size: 1.2em; } #container { display: flex; flex-direction: row; text-align: left; width: 100vw; height: 100vh; } #container h3 { font-size: 1em; text-align: center; } #container h3:after { content: ""; width: 0; height: 0; border-top: 5px solid #444; border-left: 5px solid transparent; border-right: 5px solid transparent; display: inline-block; vertical-align: middle; margin-left: 4px; } .list { display: flex; flex-direction: column; min-width: 150px; max-width: 300px; padding: 0 10px; border-right: 1px solid black; background-color: #eee; overflow-x: hidden; overflow-y: scroll; } .list h2 { font-size: 1em; font-style: italic; text-align: center; } .node { display: block; margin: 4px 0; color: #444; } .node .count { font-weight: bold; } .node.leaf { color: #aaa; } .node.open, .node:hover:not(.leaf):not(.duplicate) { background-color: #ddd; } .node.duplicate { background-color: black; color: white; } .hidden, .node.hiddenByFilter, .node.hiddenByCollapse, .node.hiddenUsed, .node.hiddenUnused { display: none; } </style> </head> <body> <div id="top-bar"> <div id="toggle-button" onclick="toggleMode()"></div> <div class="option-buttons top-bar-options"> <div> <label id="hide-unused-label" for="hide-unused">Hide Unused</label> <input type="checkbox" name="hide-unused" id="toggle-hide-unused"> </div> <div> <label id="hide-used-label" for="hide-used">Hide Used</label> <input type="checkbox" name="hide-used" id="toggle-hide-used"> </div> <div> <label for="acyclic-graph">Prevent loops</label> <input type="checkbox" name="acyclic-graph" id="toggle-acyclic" checked> </div> </div> <div class="sorting-buttons top-bar-options"> <div> <label>Sort highest</label> <input type="radio" name="sorting" class="sort-mode-button" value="sort-high" checked> </div> <div> <label>Sort lowest</label> <input type="radio" name="sorting" class="sort-mode-button" value="sort-low"> </div> <div> <label>Alphabetical</label> <input type="radio" name="sorting" class="sort-mode-button" value="alphabetical"> </div> </div> </div> <div id="container"></div> <script> var container = document.getElementById("container"); var button = document.getElementById("toggle-button"); var usedGraph = {}; var dependencyGraph = {}; var dependentsGraph = {}; var hideUsedToggle = document.getElementById("toggle-hide-used"); var hideUnusedToggle = document.getElementById("toggle-hide-unused"); var hideUsedLabel = document.getElementById("hide-used-label"); var hideUnusedLabel = document.getElementById("hide-unused-label"); var acyclicToggle = document.getElementById("toggle-acyclic"); var sortMode = "sort-high"; var dependencyList; var dependentsList; hideUsedToggle.addEventListener("change", function (event) { if (event.target.checked) { document.querySelectorAll("a.node").forEach(function(element){ if (parseInt(element.getAttribute("data-node-child-count")) > 0) { element.classList.add("hiddenUsed"); } }); } else { document.querySelectorAll(".hiddenUsed").forEach(function(element){ element.classList.remove("hiddenUsed"); }); } updateAllSublistNumbers(); }); hideUnusedToggle.addEventListener("change", function (event) { if (event.target.checked) { document.querySelectorAll("a.node").forEach(function(element){ if (parseInt(element.getAttribute("data-node-child-count")) < 1) { element.classList.add("hiddenUnused"); } }); } else { document.querySelectorAll(".hiddenUnused").forEach(function(element){ element.classList.remove("hiddenUnused"); }); } updateAllSublistNumbers(); }); document.querySelectorAll(".sort-mode-button").forEach(function(radio) { radio.onclick = function () { sortMode = radio.value; // sort existing lists var lists = []; lists.push(document.querySelector(".list[data-list-num='0']")); document.querySelectorAll(".sublist").forEach(sublist => lists.push(sublist)); lists.forEach(function(listElement) { var array = [] listElement.querySelectorAll("a.node").forEach(function(node){ array.push(node); node.parentNode.removeChild(node); }); array.sort(function(a, b) { return sort( [a.getAttribute("data-filename"), a.getAttribute("data-node-child-count")], [b.getAttribute("data-filename"), b.getAttribute("data-node-child-count")] ); }); array.forEach(function(node) { listElement.appendChild(node); }); }); } }); // start fetchJSONFile("dependencies.json", function (dependencyData) { dependencyGraph = dependencyData; fetchJSONFile("dependents.json", function (dependentData) { dependentsGraph = dependentData; initFirstLists(); usedGraph = dependentsGraph; toggleMode(); }); }); function initFirstLists () { function makeList (graph) { usedGraph = graph; var list = document.createElement("div"); list.classList.add("list"); list.setAttribute("data-list-num", 0); var filter = document.createElement("input"); filter.setAttribute("type", "text"); filter.classList.add("filter"); list.appendChild(filter); filter.addEventListener("change", function(){filterByString(filter);} ); container.appendChild(list); var array = Object.keys(graph).map(function(key) { return [key, getNumChildren(key)]; }); array.sort(function(a, b) { return sort(a, b); }); for (var i = 0; i < array.length; i++) { addNode(array[i][0], list); } if (graph !== usedGraph) { list.classList.add("hidden"); } return list; } dependencyList = makeList(dependencyGraph); dependentsList = makeList(dependentsGraph); } function toggleMode () { closeListsToTheRight(0); if (usedGraph === dependencyGraph) { usedGraph = dependentsGraph; dependentsList.classList.remove("hidden"); dependencyList.classList.add("hidden"); button.innerHTML = "<h1>Toggle Direction ←</h1><p>(X is referenced by Y)</p>"; hideUnusedLabel.innerHTML = "Hide unused"; hideUsedLabel.innerHTML = "Hide used"; } else { usedGraph = dependencyGraph; dependentsList.classList.add("hidden"); dependencyList.classList.remove("hidden"); button.innerHTML = "<h1>Toggle Direction →</h1><p>(X references Y)</p>"; hideUnusedLabel.innerHTML = "Hide items with no references"; hideUsedLabel.innerHTML = "Hide items with references"; } } function updateAllSublistNumbers() { document.querySelectorAll(".list").forEach(function (list) { updateSublistNumbers(list); }); } function updateSublistNumbers(list) { list.querySelectorAll(".sublist").forEach(function (element) { let numChildren = [...element.children].filter(node => node.classList.contains("node") && !isHidden(node)).length; element.querySelector("span.sublist-num").innerHTML = numChildren; }); } function filterByString (filter) { closeListsToTheRight(filter.parentElement.getAttribute("data-list-num")); if (!filter.value) { filter.parentElement.querySelectorAll("a.node").forEach(function(element) { element.classList.remove("hiddenByFilter"); }); } else { filter.parentElement.querySelectorAll("a.node").forEach(function(element) { if (element.getAttribute("data-filename").includes(filter.value)) { element.classList.remove("hiddenByFilter"); } else { element.classList.add("hiddenByFilter"); } }); } updateSublistNumbers(filter.closest(".list")); } function sortLowestFirst (a, b) { return a[1] - b[1]; } function sortHighestFirst (a, b) { return b[1] - a[1]; } function sortAlphabetical (a, b) { if (a[0] < b[0]) { return -1; } else if (a[0] > b[0]) { return 1; } else { return 0; } } // a and b are [filename, numChildren] function sort (a, b) { switch (sortMode) { case "sort-high": return sortHighestFirst(a, b); case "sort-low": return sortLowestFirst(a, b); case "alphabetical": return sortAlphabetical(a, b); default: return sortHighestFirst(a, b); } } function closeListsToTheRight (index) { container.querySelectorAll(".list").forEach(function(element) { if (element.getAttribute("data-list-num") > index) { element.parentNode.removeChild(element); } }); } function toggleSublist (listElement) { const filterValue = listElement.parentElement.querySelector("input.filter").value; if (listElement.classList.contains("sublist-closed")) { listElement.classList.remove("sublist-closed"); listElement.querySelectorAll("a.node").forEach(function (element) { if ((!filterValue || filterValue.length > 0) || element.getAttribute("data-filename").includes(filterValue)) { element.classList.remove("hiddenByCollapse"); } }); } else { listElement.classList.add("sublist-closed"); listElement.querySelectorAll("a.node").forEach(function (element) { element.classList.add("hiddenByCollapse"); }); } } function createSublist(listData, key, list) { listData[key].sort(function(a, b) { return sort([a, getNumChildren(a)], [b, getNumChildren(b)]); }); var sublist = document.createElement("div"); sublist.classList.add("sublist"); list.appendChild(sublist); var header = document.createElement("h3"); header.innerHTML = `${key} [<span class='sublist-num'>${listData[key].length}</span>]`; header.onclick = function () { toggleSublist(sublist); } sublist.appendChild(header); for (var i = 0; i < listData[key].length; i++) { addNode(listData[key][i], sublist); } } function createNewList(filename, num) { var listData = usedGraph[filename]; var list = document.createElement("div"); list.classList.add("list"); list.setAttribute("data-list-num", num); var header = document.createElement("h2"); if (usedGraph === dependencyGraph) { header.innerText = filename + " →"; } else { header.innerText = "← " + filename; } list.appendChild(header); var filter = document.createElement("input"); filter.setAttribute("type", "text"); filter.classList.add("filter"); list.appendChild(filter); filter.addEventListener("change", function(){filterByString(filter);} ); container.appendChild(list); for (var key in listData) { if (listData[key].length > 0) { createSublist(listData, key, list); } } } function getVisibleNodesWithFilename(filename) { return [].slice.call(document.querySelectorAll("[data-filename='" + filename + "']")).filter(element => !isHidden(element)); } function isLeaf(filename) { var dataObj = usedGraph[filename]; for (var key in dataObj) { if (dataObj[key] && dataObj[key].length > 0) { return false; } } return true; } function isHidden(element) { return element.classList.contains("hiddenByFilter") || element.classList.contains("hiddenUsed") || element.classList.contains("hiddenUnused") || element.classList.contains("hiddenByCollapse") || element.closest(".list").classList.contains("hidden"); } function getNumChildren(filename){ var dataObj = usedGraph[filename]; var count = 0; for (var key in dataObj) { count += dataObj[key].length; } return count; } function isRepeat(filename) { var instances = getVisibleNodesWithFilename(filename); return instances.length > 1; } function addNode(filename, parentElement) { if (!parentElement) { var children = container.querySelectorAll(".list"); parentElement = children[children.length - 1]; } var numChildren = getNumChildren(filename); var node = document.createElement("a"); node.classList.add("node"); node.setAttribute("data-filename", filename); node.setAttribute("data-node-child-count", numChildren); node.innerHTML = `<span class='count'>[${numChildren}]</span> ${filename} `; parentElement.appendChild(node); if (isLeaf(filename)) { node.classList.add("leaf"); if (hideUnusedToggle.checked) { node.classList.add("hiddenUnused"); } } else { if (hideUsedToggle.checked) { node.classList.add("hiddenUsed"); } if (isRepeat(filename)) { node.onmouseover = function () { document.querySelectorAll("[data-filename='" + filename + "']").forEach(function(element) { element.classList.add("duplicate"); }); }; node.onmouseout = function () { document.querySelectorAll("[data-filename='" + filename + "']").forEach(function(element) { element.classList.remove("duplicate"); }); }; } node.onclick = function() { openNode(filename, parentElement); node.classList.remove("duplicate"); } } } function openNode (filename, parentElement) { var firstNodeInstance; if (acyclicToggle.checked) { var nodeInstances = getVisibleNodesWithFilename(filename); // find oldest ancestor with the same filename nodeInstances.sort(function(a, b) { parseInt(a.parentElement.getAttribute("data-list-num")) - parseInt(b.parentElement.getAttribute("data-list-num")); }); firstNodeInstance = nodeInstances[0]; } else { firstNodeInstance = [].slice.call(parentElement.querySelectorAll("[data-filename='" + filename + "']"))[0]; } var listNum = parseInt(firstNodeInstance.closest(".list").getAttribute("data-list-num")); closeListsToTheRight(listNum); createNewList(filename, listNum + 1); firstNodeInstance.parentElement.querySelectorAll(".open").forEach(function(child) { child.classList.remove("open"); }); document.querySelectorAll("[data-filename='" + filename + "']").forEach(function(element) { element.classList.remove("duplicate"); }); firstNodeInstance.classList.add("open"); } function fetchJSONFile(path, callback) { var httpRequest = new XMLHttpRequest(); httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { if (httpRequest.status === 200 || httpRequest.status === 0) { var data = JSON.parse(httpRequest.responseText); if (callback) callback(data); } } }; httpRequest.open('GET', path); httpRequest.send(); } </script> </body>