diff --git a/display/display.css b/display/display.css index dacddf9427f0516d798316197972546caa245a3f..2d08a58c7dc620e055a6de274be5dbb2736bda08 100644 --- a/display/display.css +++ b/display/display.css @@ -2,32 +2,17 @@ display: flex; } -.detail-view-info-area::-webkit-scrollbar { +.fancy-scrollbar::-webkit-scrollbar { width: 6px; background-color: #f5f5f5; } -.detail-view-info-area::-webkit-scrollbar-track { +.fancy-scrollbar::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); background-color: #f5f5f5; } -.detail-view-info-area::-webkit-scrollbar-thumb { - background-color: darkgray; -} - -.bottom-container-tab-content::-webkit-scrollbar { - width: 6px; - height: 8px; - background-color: #f5f5f5; -} - -.bottom-container-tab-content::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); - background-color: #f5f5f5; -} - -.bottom-container-tab-content::-webkit-scrollbar-thumb { +.fancy-scrollbar::-webkit-scrollbar-thumb { background-color: darkgray; } @@ -196,81 +181,118 @@ width: 78px; } -.bottom-container-tab-content { - display: none; - /*padding: 6px 0px;*/ - /*border-top: none;*/ - overflow-x: auto; - /*height: 100px;*/ - height: 100%; +.neighbor-container { + position: absolute; + bottom: 0; + left: 0; width: 100%; + max-height: 50%; + overflow: auto; + background-color: #ffffff; } -.active-tab-nav { - color: black; +/*New Section */ + +.neighbor-collapsible-title { + background-color: #ffffff; + color: #444; + cursor: pointer; + padding: 9px 9px 9px 0; + width: 100%; + display: flex; font-weight: bold; + text-align: left; + font-size: 20px; + opacity: 100%; + border: 2px solid white; + outline: none; + flex-direction: column; + position: relative; } -.active-tab-content { +.neighbor-collapsible-section { + background-color: #ffffff; + color: #444; + cursor: pointer; + padding: 9px 9px 9px 0; + width: 100%; + border: 2px solid white; + text-align: left; + outline: none; + font-size: 15px; display: flex; - align-items: center; + flex-direction: column; + position: relative; + font-weight: bold; + opacity: 80%; } -.hidden-tab { - display: none; +.activation-hover-title, .neighbor-collapsible-title:hover { + background-color: #ccc; } -.bottom-container { - position: absolute; - bottom: 0; - left: 0; - width: 100%; +.activation-hover-section, .neighbor-collapsible-section:hover { + background-color: #ccc; } -.bottom-container-nav { - height: 35px; +.neighbor-content-tabs { + padding: 0 18px; + display: inline-block; width: 100%; - top: 0; + overflow: hidden; + background-color: #ffffff; + flex-direction: column; + margin-bottom: 10px; +} + +.neighbor-content-linksection { + padding-top: 0; + padding-left: 5px; display: flex; - flex-direction: row; + background-color: #ffffff; + flex-direction: column; + overflow: hidden; +} + +.neighbor-content-link { cursor: pointer; + margin: 1px; + font-size: 14px; +} + +.activation-hover, .neighbor-content-link:hover { + color: red; } -.bottom-container-links { +.neighbor-collapsible-marker-div { + position: absolute; width: 100%; - height: 100px; - display: block; + height: 2px; + border: 1px solid red; + bottom: 0; + left: 0; } -.bottom-container-link-text { - font-size: 8px; - /*font-size: 10px;*/ - max-width: 65px; - font-weight: bold; - text-align: center; +.neighbor-collapsible-open-status-marker { + position: absolute; + right: 20px; + font-size: 26px; + color: #595858; + top: 2px; + bottom: 2px; } -.link-img { - min-width: 70px; - width: 70px; - height: 70px; - margin: 10px; - pointer-events: all; - cursor: pointer; - border-radius: 50%; - overflow: hidden; - background-color: white; - display: flex; - align-items: center; - justify-content: center; +.neighbor-tab-open-status-marker { + position: absolute; + right: 20px; + font-size: 15px; + color: #595858; + top: 10px; + bottom: 10px; } -.bottom-container-nav-tab { - height: 100%; - display: flex; - padding: 0 8px; - min-width: 10%; - text-align: center; - line-height: 35px; - font-size: 0.7vw; +.neighbor-collapsible-wrapper { + transition:height 0.3s ease-out; + height:0; + overflow:hidden; } diff --git a/display/overlays/filteroverlay.js b/display/overlays/filteroverlay.js index 36c1cbb4cd58424c4e7c0f306128576013e48ca1..06d68042cc70fbca89e331ddffda23b9d68af02e 100644 --- a/display/overlays/filteroverlay.js +++ b/display/overlays/filteroverlay.js @@ -74,5 +74,6 @@ class FilterOverlay { } else { target.style.opacity = 1.0; } + this.graph.infoOverlay.bottomMenu.toggleCategory(target.type); } } diff --git a/display/overlays/neighbors.js b/display/overlays/neighbors.js index 5eb729d64b40928f87bbe23f19e485ee3ffc9584..9a6301a6bfac69d2f300702870a55882ce53cc60 100644 --- a/display/overlays/neighbors.js +++ b/display/overlays/neighbors.js @@ -1,6 +1,5 @@ import * as Helpers from "../helpers"; -import jQuery from "jquery"; -import * as Config from "../../config"; +import { createHTMLElement } from "../helpers"; export { NodeNeighborOverlay }; @@ -14,12 +13,11 @@ class NodeNeighborOverlay { this.parentNode = parentNode; this.infoOverlay = infoOverlay; this.type = type; + this.contentTab = null; - this.activeTabNav = null; // The currently selected tab handle - this.activeTabContent = null; // The currently selected tab content - - this.tabContentPages = {}; - this.tabNavHandles = {}; + this.tabContentPages = {}; // Page content - links to other nodes + this.tabNavHandles = {}; // Top-level handles of the content pages + this.tabPageVisibility = {}; // Visibility of each content page } /** @@ -28,139 +26,145 @@ class NodeNeighborOverlay { */ create() { const bottomContainerDiv = Helpers.createDiv( - "bottom-container", + "neighbor-container", this.parentNode ); - const bottomContainerNavDiv = Helpers.createDiv( - "bottom-container-nav", - bottomContainerDiv - ); - const bottomContainerLinkDiv = Helpers.createDiv( - "bottom-container-links", + 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)) { - const navTab = Helpers.createDiv( - "bottom-container-nav-tab", - bottomContainerNavDiv - ); - navTab.innerText = cls.slice(0, 3); - navTab.style.backgroundColor = color; - navTab.type = cls; // Attach the edge type to the DOM object to retrieve it during click events - jQuery(navTab).click((event) => this.openTabFromEvent(event)); - this.tabNavHandles[cls] = navTab; - - const tabContent = Helpers.createDiv( - "bottom-container-tab-content", - bottomContainerLinkDiv - ); - tabContent.style.backgroundColor = color; - this.tabContentPages[cls] = tabContent; + this.createCollapsibleTab(contentTab, cls, color); } - this.initializeActive(bottomContainerNavDiv, bottomContainerLinkDiv); } /** - * Initializes the activeTabNav and activeTabContent variables to a random edge type. - * @param {Element} bottomContainerNavDiv - * @param {Element} bottomContainerLinkDiv + * 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 */ - initializeActive(bottomContainerNavDiv, bottomContainerLinkDiv) { - this.activeTabNav = bottomContainerNavDiv.firstChild; - this.activeTabContent = bottomContainerLinkDiv.firstChild; + 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; - this.activeTabContent.classList.add("active-tab-content"); - this.activeTabNav.classList.add("active-tab-nav"); - this.activeTabNav.innerText = this.activeTabNav.type; - } + const openMarkerTabs = Helpers.createDiv( + "neighbor-tab-open-status-marker", + collTab + ); + openMarkerTabs.innerText = "+"; + collTab.marker = openMarkerTabs; - /** - * Click event handler for the tab headers of the bottom menu. - * @param event - */ - openTabFromEvent(event) { - const navTab = event.target; - const cls = navTab.type; - this.openTab(cls); - } + // Content of the different tabs + const collTabContent = Helpers.createDiv( + "neighbor-content-linksection", + parent + ); + collTabContent.classList.add("neighbor-collapsible-wrapper"); + collTabContent.type = name; - openTab(cls) { - this.activeTabNav.classList.remove("active-tab-nav"); - this.activeTabNav.innerText = this.activeTabNav.innerText.slice(0, 3); - this.tabNavHandles[cls].classList.add("active-tab-nav"); - this.tabNavHandles[cls].innerText = cls; + const list = createHTMLElement("ul", collTabContent); + list.style.margin = "0px"; - this.activeTabContent.classList.remove("active-tab-content"); - this.tabContentPages[cls].classList.add("active-tab-content"); + collTabContent.list = list; + collTabContent.marker = openMarkerTabs; + this.tabContentPages[name] = collTabContent; - this.activeTabNav = this.tabNavHandles[cls]; - this.activeTabContent = this.tabContentPages[cls]; + collTab.addEventListener("click", () => { + this.toggleSectionVisibility(name); + }); } - toggleTabVisibility(cls) { - this.tabNavHandles[cls].classList.toggle("bottom-container-nav-tab"); - this.tabNavHandles[cls].classList.toggle("hidden-tab"); - - const tcc = this.tabContentPages[cls].classList; - tcc.toggle("bottom-container-tab-content"); - tcc.toggle("hidden-tab"); - - // If the tab gets hidden and is the active tab, search for an alternative nav tab to become the new active tab. - if (tcc.contains("hidden-tab")) { - if (tcc.contains("active-tab-nav")) { - for (const tab of Object.values(this.tabNavHandles)) { - if (!tab.classList.contains("hidden-tab")) { - this.openTab(tab.type); - break; - } - } - } + /** + * Toggles the visibility of a specific section + * @param {string} name Id of the section + */ + toggleSectionVisibility(name) { + if (this.tabPageVisibility[name]) { + this.collapseSection(name); } else { - // If all tabs are hidden, the new tab should become the active tab. - if (this.activeTabNav.classList.contains("hidden-tab")) { - this.openTab(cls); - } + this.expandSection(name); } } /** - * Clears the images from all tab content pages. + * 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)) { - jQuery(page).empty(); + page.list.replaceChildren(); } } /** - * Creates a new image (with link) for the given target node. + * Creates a new list element for the given target node. * @param target - * @returns {HTMLDivElement} + * @returns {HTMLLIElement} */ createReference(target) { - const linkDiv = document.createElement("div"); - linkDiv.className = "link-img"; - - if ("image" in target) { - const linkImage = document.createElement("img"); - linkImage.src = - Config.PLUGIN_PATH + "datasets/images/" + target.image; - linkDiv.appendChild(linkImage); - } - - if ("name" in target) { - Helpers.createHTMLElement("p", linkDiv, { - className: "bottom-container-link-text", - innerText: target.name, - }); - } - - jQuery(linkDiv).on("click", () => { + const linkDiv = document.createElement("li"); + const linkText = document.createTextNode(target.name); + linkDiv.className = "neighbor-content-link"; + linkDiv.appendChild(linkText); + linkDiv.addEventListener("click", () => { this.graph.focusOnNode(target); this.infoOverlay.updateInfoOverlay(target); }); @@ -173,15 +177,51 @@ class NodeNeighborOverlay { */ updateTabs(node) { this.clearTabContentPages(); - for (const link of node.links) { - const target = link.source == node ? link.target : link.source; + const target = link.source === node ? link.target : link.source; const reference = this.createReference(target); if (this.type === "link") { - this.tabContentPages[link.type].appendChild(reference); + this.tabContentPages[link.type].list.appendChild(reference); } else { - this.tabContentPages[target.type].appendChild(reference); + 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"; + } } } diff --git a/display/overlays/nodeinfo.js b/display/overlays/nodeinfo.js index f588fc6b0e83aa0676fb661dae8c757b5f032354..40c02e429c660808a6437fbd7d1b4ae5abce2737 100644 --- a/display/overlays/nodeinfo.js +++ b/display/overlays/nodeinfo.js @@ -1,6 +1,6 @@ import jQuery from "jquery"; -// import { NodeNeighborOverlay } from "./neighbors"; +import { NodeNeighborOverlay } from "./neighbors"; import * as Helpers from "../helpers"; import * as Config from "../../config"; @@ -16,7 +16,7 @@ class NodeInfoOverlay { */ constructor(graph) { this.graph = graph; - // this.bottomMenu = null; + this.bottomMenu = null; } /** @@ -26,13 +26,13 @@ class NodeInfoOverlay { create() { const overlayDiv = this.createOverlayMainDiv(); this.createOverlayElements(overlayDiv); - // this.bottomMenu = new NodeNeighborOverlay( - // this.graph, - // overlayDiv, - // this, - // "node" - // ); - // this.bottomMenu.create(); + this.bottomMenu = new NodeNeighborOverlay( + this.graph, + overlayDiv, + this, + "node" + ); + this.bottomMenu.create(); jQuery("#infoOverlayCloseButton").click(function () { jQuery("#infoOverlay").slideUp("fast"); @@ -59,6 +59,7 @@ class NodeInfoOverlay { "detail-view-info-area", overlayNode ); + infoArea.classList.add("fancy-scrollbar"); const topArea = Helpers.createDiv("detail-view-top-area", infoArea); @@ -170,7 +171,7 @@ class NodeInfoOverlay { linkArea.hide(); } - // this.bottomMenu.updateTabs(node); + this.bottomMenu.updateTabs(node); jQuery("#infoOverlay").slideDown("fast"); } }