import * as Helpers from "../helpers"; import { createHTMLElement } from "../helpers"; export { NodeNeighborOverlay }; /** * Displays an overlay showing the neighbors of a node divided by the link type * that connects them. */ class NodeNeighborOverlay { constructor(graph, parentNode, infoOverlay, type = "link") { this.graph = graph; this.parentNode = parentNode; this.infoOverlay = infoOverlay; this.type = type; this.contentTab = null; this.tabContentPages = {}; // Page content - links to other nodes this.tabNavHandles = {}; // Top-level handles of the content pages this.tabPageVisibility = {}; // Visibility of each content page } /** * Creates the visible elements of the overlay. * Must be called after the graph has been initialized. */ create() { const bottomContainerDiv = Helpers.createDiv( "neighbor-container", this.parentNode ); bottomContainerDiv.classList.add("fancy-scrollbar"); // Create the collapsible of the entire menu const coll = Helpers.createDiv("button", bottomContainerDiv); coll.className = "neighbor-collapsible-title"; coll.innerText = "Verwandte Inhalte"; coll.style.textAlign = "center"; // Div that displays the information about all the chapters const contentTab = Helpers.createDiv( "neighbor-content-tabs", bottomContainerDiv ); //contentTab.classList.add("neighbor-collapsible-wrapper"); this.contentTab = contentTab; coll.addEventListener("click", () => { if (contentTab.style.height === "0px") { contentTab.style.height = "auto"; } else { contentTab.style.height = "0px"; } }); const colors = this.type === "link" ? this.graph.edgeColors : this.graph.nodeColors; for (const [cls, color] of Object.entries(colors)) { this.createCollapsibleTab(contentTab, cls, color); } } /** * Creates a new collapsible tab and content area for a specific node. * @param {HTMLElement} parent Parent of the new tab. * @param {string} name Name of the node type class * @param {string} color Color of the node type class */ createCollapsibleTab(parent, name, color) { // Creating the collapsible tabs for the different chapters const collTab = Helpers.createDiv("button", parent); collTab.className = "neighbor-collapsible-section"; collTab.innerText = name; collTab.type = name; this.tabNavHandles[name] = collTab; this.tabPageVisibility[name] = false; const collTabMarker = Helpers.createDiv( "neighbor-collapsible-marker-div", collTab ); collTabMarker.style.borderColor = color; collTabMarker.style.backgroundColor = color; const openMarkerTabs = Helpers.createDiv( "neighbor-tab-open-status-marker", collTab ); openMarkerTabs.innerText = "+"; collTab.marker = openMarkerTabs; // Content of the different tabs const collTabContent = Helpers.createDiv( "neighbor-content-linksection", parent ); collTabContent.classList.add("neighbor-collapsible-wrapper"); collTabContent.type = name; const list = createHTMLElement("ul", collTabContent); list.style.margin = 0; collTabContent.list = list; collTabContent.marker = openMarkerTabs; this.tabContentPages[name] = collTabContent; collTab.addEventListener("click", () => { this.toggleSectionVisibility(name); }); } /** * Toggles the visibility of a specific section * @param {string} name Id of the section */ toggleSectionVisibility(name) { if (this.tabPageVisibility[name]) { this.collapseSection(name); } else { this.expandSection(name); } } /** * Collapses the content area of a specific section * @param {string} name Id of the section */ collapseSection(name) { this.tabPageVisibility[name] = false; const section = this.tabContentPages[name]; section.style.height = "0px"; section.marker.innerText = "+"; } /** * Expands the content area of a specific section * @param {string} name Id of the section */ expandSection(name) { this.tabPageVisibility[name] = true; const section = this.tabContentPages[name]; section.style.height = `${section.scrollHeight}px`; section.marker.innerText = "-"; } /** * Clears the images from all tab content pages and makes the object * invisible. */ clearTabContentPages() { for (const page of Object.values(this.tabContentPages)) { page.list.replaceChildren(); } } /** * Creates a new list element for the given target node. * @param target * @returns {HTMLDivElement} */ createReference(target) { const linkDiv = document.createElement("li"); var linkText = document.createTextNode(target.name); linkDiv.className = "neighbor-content-link"; linkDiv.appendChild(linkText); linkDiv.addEventListener("click", () => { this.graph.focusOnNode(target); this.infoOverlay.updateInfoOverlay(target); }); return linkDiv; } /** * Updates all tabs to have content matching the given node. * @param node */ updateTabs(node) { this.clearTabContentPages(); for (const link of node.links) { const target = link.source === node ? link.target : link.source; const reference = this.createReference(target); if (this.type === "link") { this.tabContentPages[link.type].list.appendChild(reference); } else { this.tabContentPages[target.type].list.appendChild(reference); } } this.updatePageVisibility(); } /** * Updates the content page visibility on node change. * Hides all empty content pages. */ updatePageVisibility() { for (const page of Object.values(this.tabContentPages)) { if (!page.list.hasChildNodes()) { this.tabNavHandles[page.type].style.display = "none"; page.style.display = "none"; } else { this.tabNavHandles[page.type].style.display = "flex"; page.style.display = "flex"; if (this.tabPageVisibility[page.type]) { page.style.height = `${page.list.scrollHeight}px`; } } } } /** * Toggle the visibility for a category * @param {string} type The name of the category that should be toggled */ toggleCategory(type) { const page = this.tabContentPages[type]; const handle = this.tabNavHandles[type]; if (handle.style.display === "flex") { page.style.display = "none"; handle.style.display = "none"; } else if (page.list.hasChildNodes()) { page.style.display = "flex"; handle.style.display = "flex"; } } }