From 053a675188f65baed4897e1965cc80d1af97d6cb Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Mon, 11 Sep 2023 13:18:43 -0400 Subject: [PATCH 1/8] Add nested groups support in side nav --- .../site/generatr/site/SiteGenerator.kt | 2 + .../site/generatr/site/model/MenuViewModel.kt | 26 ++++++++ .../site/generatr/site/model/PageViewModel.kt | 7 +- .../site/generatr/site/views/Menu.kt | 58 +++++++++++++++-- .../site/generatr/site/views/Page.kt | 24 ++++++- src/main/resources/assets/css/treeview.css | 35 ++++++++++ src/main/resources/assets/js/treeview.js | 65 +++++++++++++++++++ 7 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 src/main/resources/assets/css/treeview.css create mode 100644 src/main/resources/assets/js/treeview.js diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt index 6909c745..cde19af0 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt @@ -19,6 +19,8 @@ fun copySiteWideAssets(exportDir: File) { copySiteWideAsset(exportDir, "/css/admonition.css") copySiteWideAsset(exportDir, "/js/admonition.js") copySiteWideAsset(exportDir, "/js/reformat-mermaid.js") + copySiteWideAsset(exportDir, "/css/treeview.css") + copySiteWideAsset(exportDir, "/js/treeview.js") } private fun copySiteWideAsset(exportDir: File, asset: String) { diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt index 6cb888e6..af495c87 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt @@ -27,4 +27,30 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode private fun createMenuItem(title: String, href: String, exact: Boolean = true) = LinkViewModel(pageViewModel, title, href, exact) + + data class Node(val name: String, val children: MutableList) + + val nestedSoftwareSystems = generatorContext.workspace.model.includedSoftwareSystems + .map {it.group + "/" + it.name} + .sortedBy {it.lowercase()} + + fun buildTree(data: List, delimiter: Char):MutableList { + val rootNode = Node("", mutableListOf()) + for (path in data) { + val parts = path.split(delimiter) + var currentNode = rootNode + + for (part in parts) { + val existingNode = currentNode.children.find { it.name == part } + currentNode = if (existingNode == null) { + val newNode = Node(part, mutableListOf()) + currentNode.children.add(newNode) + newNode + } else { + existingNode + } + } + } + return rootNode.children + } } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt index bbbdb27f..21c02780 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt @@ -15,14 +15,15 @@ abstract class PageViewModel(protected val generatorContext: GeneratorContext) { val headerBar by lazy { HeaderBarViewModel(this, generatorContext) } val menu by lazy { MenuViewModel(generatorContext, this) } val includeAutoReloading = generatorContext.serving - - val flexmarkConfig by lazy { - buildFlexmarkConfig(generatorContext) + + val flexmarkConfig by lazy { + buildFlexmarkConfig(generatorContext) } val includeAdmonition = flexmarkConfig.selectedExtensionMap.containsKey("Admonition") val includeKatex = flexmarkConfig.selectedExtensionMap.containsKey("GitLab") val configuration = generatorContext.workspace.views.configuration.properties + val includeTreeview = configuration.getOrDefault("generatr.nav.nestGroups", "false").toBoolean() abstract val url: String abstract val pageSubTitle: String diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt index fe6c2a6e..cc57289d 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt @@ -1,13 +1,14 @@ package nl.avisi.structurizr.site.generatr.site.views import kotlinx.html.* +import nl.avisi.structurizr.site.generatr.site.GeneratorContext import nl.avisi.structurizr.site.generatr.site.model.LinkViewModel import nl.avisi.structurizr.site.generatr.site.model.MenuViewModel -fun DIV.menu(viewModel: MenuViewModel) { +fun DIV.menu(viewModel: MenuViewModel, nestGroups:Boolean) { aside(classes = "menu p-3") { generalSection(viewModel.generalItems) - softwareSystemsSection(viewModel.softwareSystemItems) + softwareSystemsSection(viewModel, nestGroups) } } @@ -16,9 +17,18 @@ private fun ASIDE.generalSection(items: List) { menuItemLinks(items) } -private fun ASIDE.softwareSystemsSection(items: List) { +private fun ASIDE.softwareSystemsSection(viewModel: MenuViewModel, nestGroups:Boolean) { p(classes = "menu-label") { +"Software systems" } - menuItemLinks(items) + if(nestGroups){ + // Display SoftwareSystems as a nested lists + val rootNode = MenuViewModel.Node("", viewModel.buildTree(viewModel.nestedSoftwareSystems, "/".toCharArray()[0])) + ul(classes = "listree menu-list has-site-branding"){ + buildHtmlTree(rootNode, viewModel).invoke(this) + } + } else { + // Display SoftwareSystems as a flat list + menuItemLinks(viewModel.softwareSystemItems) + } } private fun ASIDE.menuItemLinks(items: List) { @@ -30,3 +40,43 @@ private fun ASIDE.menuItemLinks(items: List) { } } } + +private fun buildHtmlTree(node: MenuViewModel.Node, viewModel: MenuViewModel): UL.() -> Unit = { + + if (node.name.isNotEmpty() && node.children.isEmpty()) { + val itemLink = viewModel.softwareSystemItems.find { it.title == node.name } + li { + if (itemLink != null) { + link(itemLink) + } + } + } + + if (node.name.isNotEmpty() && node.children.isNotEmpty()) { + li { + if(node.name == "null"){ + div(classes = "listree-submenu-heading") { + +"No Group" + } + } else { + div(classes = "listree-submenu-heading") { + +node.name + } + } + + ul(classes = "listree-submenu-items") { + for (child in node.children) { + buildHtmlTree(child,viewModel).invoke(this) + } + } + } + } + if (node.name.isEmpty() && node.children.isNotEmpty()) { + ul(classes = "listree-submenu-items") { + for (child in node.children) { + buildHtmlTree(child, viewModel).invoke(this) + } + } + } + +} diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt index e013ab8b..b66c7c06 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt @@ -27,7 +27,14 @@ private fun HTML.headFragment(viewModel: PageViewModel) { href = "./" + "/style-branding.css".asUrlToFile(viewModel.url) ) - if (viewModel.includeAdmonition) + if(viewModel.includeTreeview){ + link( + rel = "stylesheet", + href = "../" + "/treeview.css".asUrlToFile(viewModel.url) + ) + } + + if (viewModel.includeAdmonition) markdownAdmonitionStylesheet(viewModel) if (viewModel.includeKatex) @@ -54,7 +61,7 @@ private fun HTML.bodyFragment(viewModel: PageViewModel, block: DIV.() -> Unit) { div(classes = "site-layout") { id = "site" - menu(viewModel.menu) + menu(viewModel.menu, viewModel.includeTreeview) div(classes = "container is-fluid has-background-white") { block() } @@ -64,6 +71,19 @@ private fun HTML.bodyFragment(viewModel: PageViewModel, block: DIV.() -> Unit) { updateSiteErrorHero() if (viewModel.includeAdmonition) markdownAdmonitionScript(viewModel) + if (viewModel.includeTreeview) + mermaidScript(viewModel) + + if (viewModel.includeTreeview){ + script( + type = ScriptType.textJavaScript, + src = "../" + "/treeview.js".asUrlToFile(viewModel.url) + ) { } + + script( + type = ScriptType.textJavaScript + ) { unsafe { +"listree();" } } + } } } diff --git a/src/main/resources/assets/css/treeview.css b/src/main/resources/assets/css/treeview.css new file mode 100644 index 00000000..12ed7aff --- /dev/null +++ b/src/main/resources/assets/css/treeview.css @@ -0,0 +1,35 @@ +.listree-submenu-heading { + cursor: pointer +} + +ul.listree { + list-style: none +} + +ul.listree-submenu-items { + list-style: none; + white-space: nowrap; + padding-left: 10px +} + +div.listree-submenu-heading.collapsed:before { + content: "\25B6"; + margin-right: 4px +} + +div.listree-submenu-heading.expanded:before { + content: "\25BC"; + margin-right: 4px +} + +.scrollable-menu { + height: auto; + max-width: 800px; + overflow-y: hidden +} + +.menu-list li ul { + border-left: 0; + margin: .25em; + padding-left: .75em; +} diff --git a/src/main/resources/assets/js/treeview.js b/src/main/resources/assets/js/treeview.js new file mode 100644 index 00000000..33c21fe7 --- /dev/null +++ b/src/main/resources/assets/js/treeview.js @@ -0,0 +1,65 @@ +function listree() { + + const subMenuHeadingClass = "listree-submenu-heading"; + const expandedClass = "expanded"; + const collapsedClass = "collapsed"; + const activeClass = "is-active"; + const subMenuHeadings = document.getElementsByClassName(subMenuHeadingClass); + + Array + .from(subMenuHeadings) + .forEach(function(subMenuHeading) { + // Collapse all the subMenuHeadings while searching for is-active class + let foundActive = false; + subMenuHeading.classList.add(collapsedClass); + subMenuHeading.nextElementSibling.style.display = "none"; + + // Check if this sub-menu heading is active + const liElements = subMenuHeading.nextElementSibling.children; + for (let i = 0; i < liElements.length; i++) { + if (liElements[i].hasChildNodes()) { + if (liElements[i].children[0].tagName === "A") { + if (liElements[i].children[0].classList.contains(activeClass)) { + foundActive = true; + break; + } + } + } + } + + // Expand all parent sub-menus until root menu is reached + if (foundActive) { + subMenuHeading.classList.remove(collapsedClass); + subMenuHeading.classList.add(expandedClass); + subMenuHeading.nextElementSibling.style.display = "block"; + + let currentSubMenu = subMenuHeading.parentElement; + while (currentSubMenu !== null && !currentSubMenu.classList.contains("listree")) { + if(currentSubMenu.tagName === "UL"){ + if(currentSubMenu.previousElementSibling != null) { + currentSubMenu.previousElementSibling.classList.remove(collapsedClass); + currentSubMenu.previousElementSibling.classList.add(expandedClass); + currentSubMenu.style.display = "block"; + } + } + currentSubMenu = currentSubMenu.parentElement + } + } + + // Add the eventlistener to all subMenuHeadings + subMenuHeading.addEventListener("click", function(event) { + event.preventDefault(); + const subMenuList = event.target.nextElementSibling; + if (subMenuList.style.display === "none") { + subMenuHeading.classList.remove(collapsedClass); + subMenuHeading.classList.add(expandedClass); + subMenuHeading.nextElementSibling.style.display = "block"; + } else { + subMenuHeading.classList.remove(expandedClass); + subMenuHeading.classList.add(collapsedClass); + subMenuHeading.nextElementSibling.style.display = "none"; + } + event.stopPropagation(); + }); + }); +} From e5a3db4226b35014f59985053c5510a0b3786c66 Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Mon, 11 Sep 2023 13:30:44 -0400 Subject: [PATCH 2/8] Update README.md to reference the new option. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 314df5af..c1d595c2 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ architecture model: | `generatr.search.language` | Indexing/stemming language for the search index. See [Lunr language support](https://github.com/olivernn/lunr-languages) | `en` | `nl` | | `generatr.markdown.flexmark.extensions` | Additional extensions to the markdown generator to add new markdown capabilities. [More Details](https://avisi-cloud.github.io/structurizr-site-generatr/main/extended-markdown-features/) | Tables | `Tables,Admonition` | | `generatr.svglink.target` | Specifies the link target for element links in the exported svg | `_top` | `_self` | +| `generatr.nav.nestgroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` | See the included example for usage of some those properties in the From 1d2bbfa42d56d20920c209c9fcb4fbf227a25fc7 Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Thu, 21 Sep 2023 12:50:48 -0400 Subject: [PATCH 3/8] minor change to fix case on configuration property to match the reference in the readme --- .../avisi/structurizr/site/generatr/site/model/PageViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt index 21c02780..b4b6019e 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt @@ -23,7 +23,7 @@ abstract class PageViewModel(protected val generatorContext: GeneratorContext) { val includeKatex = flexmarkConfig.selectedExtensionMap.containsKey("GitLab") val configuration = generatorContext.workspace.views.configuration.properties - val includeTreeview = configuration.getOrDefault("generatr.nav.nestGroups", "false").toBoolean() + val includeTreeview = configuration.getOrDefault("generatr.nav.nestgroups", "false").toBoolean() abstract val url: String abstract val pageSubTitle: String From 310fcd21f3895dc7f5db838fcccf5e9bd156b49e Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Thu, 21 Sep 2023 13:01:47 -0400 Subject: [PATCH 4/8] Minor update to the nested menu css to bring it closer to the standard non-nested list. --- src/main/resources/assets/css/treeview.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/css/treeview.css b/src/main/resources/assets/css/treeview.css index 12ed7aff..9473fbfa 100644 --- a/src/main/resources/assets/css/treeview.css +++ b/src/main/resources/assets/css/treeview.css @@ -1,5 +1,6 @@ .listree-submenu-heading { - cursor: pointer + cursor: pointer; + padding: .5em .75em; } ul.listree { From 15b75c7256beaf1aa6b3bb8d80bc703d409659c3 Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Thu, 12 Oct 2023 16:49:34 -0400 Subject: [PATCH 5/8] Implemented suggested changes --- README.md | 2 +- .../generatr/site/model/MenuNodeViewModel.kt | 3 ++ .../site/generatr/site/model/MenuViewModel.kt | 29 +++++++++++-------- .../site/generatr/site/model/PageViewModel.kt | 2 +- .../site/generatr/site/views/Menu.kt | 8 ++--- .../site/generatr/site/views/Page.kt | 2 +- 6 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuNodeViewModel.kt diff --git a/README.md b/README.md index 27509478..21d307e6 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ architecture model: | `generatr.search.language` | Indexing/stemming language for the search index. See [Lunr language support](https://github.com/olivernn/lunr-languages) | `en` | `nl` | | `generatr.markdown.flexmark.extensions` | Additional extensions to the markdown generator to add new markdown capabilities. [More Details](https://avisi-cloud.github.io/structurizr-site-generatr/main/extended-markdown-features/) | Tables | `Tables,Admonition` | | `generatr.svglink.target` | Specifies the link target for element links in the exported svg | `_top` | `_self` | -| `generatr.nav.nestgroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` | +| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` | See the included example for usage of some those properties in the diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuNodeViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuNodeViewModel.kt new file mode 100644 index 00000000..3bc1351f --- /dev/null +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuNodeViewModel.kt @@ -0,0 +1,3 @@ +package nl.avisi.structurizr.site.generatr.site.model + +data class MenuNodeViewModel(val name: String, val children: List) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt index af495c87..08c3cd1c 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt @@ -28,22 +28,19 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode private fun createMenuItem(title: String, href: String, exact: Boolean = true) = LinkViewModel(pageViewModel, title, href, exact) - data class Node(val name: String, val children: MutableList) + fun softwareSystemNodes(): MenuNodeViewModel { + data class MutableMenuNode(val name: String, val children: MutableList) { + fun toMenuNode(): MenuNodeViewModel = MenuNodeViewModel(name, children.map { it.toMenuNode() }) + } - val nestedSoftwareSystems = generatorContext.workspace.model.includedSoftwareSystems - .map {it.group + "/" + it.name} - .sortedBy {it.lowercase()} + val rootNode = MutableMenuNode("", mutableListOf()) - fun buildTree(data: List, delimiter: Char):MutableList { - val rootNode = Node("", mutableListOf()) - for (path in data) { - val parts = path.split(delimiter) + softwareSystemPaths.forEach { path -> var currentNode = rootNode - - for (part in parts) { + path.split(delimiter).forEach { part -> val existingNode = currentNode.children.find { it.name == part } currentNode = if (existingNode == null) { - val newNode = Node(part, mutableListOf()) + val newNode = MutableMenuNode(part, mutableListOf()) currentNode.children.add(newNode) newNode } else { @@ -51,6 +48,14 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode } } } - return rootNode.children + + return rootNode.toMenuNode() } + + private val softwareSystemPaths = generatorContext.workspace.model.includedSoftwareSystems + .map { it.group + "/" + it.name } + .sortedBy { it.lowercase() } + + private val delimiter = '/' + } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt index cb8532f6..6c779a4b 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/PageViewModel.kt @@ -20,7 +20,7 @@ abstract class PageViewModel(protected val generatorContext: GeneratorContext) { val includeAdmonition = flexmarkConfig.selectedExtensionMap.containsKey("Admonition") val includeKatex = flexmarkConfig.selectedExtensionMap.containsKey("GitLab") val configuration = generatorContext.workspace.views.configuration.properties - val includeTreeview = configuration.getOrDefault("generatr.nav.nestgroups", "false").toBoolean() + val includeTreeview = configuration.getOrDefault("generatr.site.nestGroups", "false").toBoolean() abstract val url: String abstract val pageSubTitle: String diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt index cc57289d..438e8970 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt @@ -3,6 +3,7 @@ package nl.avisi.structurizr.site.generatr.site.views import kotlinx.html.* import nl.avisi.structurizr.site.generatr.site.GeneratorContext import nl.avisi.structurizr.site.generatr.site.model.LinkViewModel +import nl.avisi.structurizr.site.generatr.site.model.MenuNodeViewModel import nl.avisi.structurizr.site.generatr.site.model.MenuViewModel fun DIV.menu(viewModel: MenuViewModel, nestGroups:Boolean) { @@ -20,13 +21,10 @@ private fun ASIDE.generalSection(items: List) { private fun ASIDE.softwareSystemsSection(viewModel: MenuViewModel, nestGroups:Boolean) { p(classes = "menu-label") { +"Software systems" } if(nestGroups){ - // Display SoftwareSystems as a nested lists - val rootNode = MenuViewModel.Node("", viewModel.buildTree(viewModel.nestedSoftwareSystems, "/".toCharArray()[0])) ul(classes = "listree menu-list has-site-branding"){ - buildHtmlTree(rootNode, viewModel).invoke(this) + buildHtmlTree(viewModel.softwareSystemNodes(), viewModel).invoke(this) } } else { - // Display SoftwareSystems as a flat list menuItemLinks(viewModel.softwareSystemItems) } } @@ -41,7 +39,7 @@ private fun ASIDE.menuItemLinks(items: List) { } } -private fun buildHtmlTree(node: MenuViewModel.Node, viewModel: MenuViewModel): UL.() -> Unit = { +private fun buildHtmlTree(node: MenuNodeViewModel, viewModel: MenuViewModel): UL.() -> Unit = { if (node.name.isNotEmpty() && node.children.isEmpty()) { val itemLink = viewModel.softwareSystemItems.find { it.title == node.name } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt index 75ac1976..3eebcc3b 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Page.kt @@ -21,7 +21,7 @@ private fun HTML.headFragment(viewModel: PageViewModel) { link(rel = "stylesheet", href = "../" + "/style.css".asUrlToFile(viewModel.url)) link(rel = "stylesheet", href = "./" + "/style-branding.css".asUrlToFile(viewModel.url)) - if(viewModel.includeTreeview) + if (viewModel.includeTreeview) link(rel = "stylesheet", href = "../" + "/treeview.css".asUrlToFile(viewModel.url)) if (viewModel.includeAdmonition) From 407e10abca9b69e22981aa62d0906a0e09ee5aeb Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Thu, 12 Oct 2023 17:08:20 -0400 Subject: [PATCH 6/8] Added a test for MenuViewModel.SoftwareSystemsNodes --- .../generatr/site/model/MenuViewModelTest.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt index adc62157..094dde89 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt @@ -136,6 +136,27 @@ class MenuViewModelTest : ViewModelTest() { } } + @Test + fun `show nested groups in software systems list`() { + val generatorContext = generatorContext(branches = listOf("main", "branch-2"), currentBranch = "main") + generatorContext.workspace.views.configuration.addProperty("generatr.site.nestGroups","true") + generatorContext.workspace.model.addSoftwareSystem("System 1").group = "Group 1" + generatorContext.workspace.model.addSoftwareSystem("System 2").group = "Group 1" + generatorContext.workspace.model.addSoftwareSystem("System 3").group = "Group 2" + generatorContext.workspace.model.addSoftwareSystem("System 4").group = "Group 1/Group 3" + + MenuViewModel(generatorContext, createPageViewModel(generatorContext, url = HomePageViewModel.url())) + .let { + assertThat(it.softwareSystemNodes().children).hasSize(2) + assertThat(it.softwareSystemNodes().children[0].name).isEqualTo("Group 1") + assertThat(it.softwareSystemNodes().children[0].children).hasSize(3) + assertThat(it.softwareSystemNodes().children[0].children[0].name).isEqualTo("Group 3") + assertThat(it.softwareSystemNodes().children[0].children[0].children[0].name).isEqualTo("System 4") + } + + + } + private fun createPageViewModel(generatorContext: GeneratorContext, url: String = "/master/page"): PageViewModel { return object : PageViewModel(generatorContext) { override val url = url From 73d146d10c06b5090d3e5b08d2e0d7086f9840b3 Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Fri, 13 Oct 2023 09:39:14 -0400 Subject: [PATCH 7/8] Fix some whitespace issues and reformat table in readme file. --- README.md | 8 ++++---- .../structurizr/site/generatr/site/model/MenuViewModel.kt | 1 - .../nl/avisi/structurizr/site/generatr/site/views/Menu.kt | 2 +- .../site/generatr/site/model/MenuViewModelTest.kt | 1 - 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 21d307e6..e862ce91 100644 --- a/README.md +++ b/README.md @@ -261,10 +261,10 @@ architecture model: | `generatr.style.faviconPath` | Site logo location relative to the configured `assets` folder. When configured, the logo image will be place on the left side in the header bar. This requires the `--assets-dir` switch when generating the site and the corresponding file to be available in the `assets` folder. | | `site/favicon.ico` | | `generatr.style.logoPath` | Site favicon location relative to the configured `assets` folder. When configured, the favicon will be set for all generated pages. This requires the `--assets-dir` switch when generating the site and the corresponding file to be available in the `assets` folder. | | `site/logo.png` | | `generatr.style.customStylesheet` | URL to hosted custom stylesheet or path to custom stylesheet file (location relative to the configured `assets` folder). When configured this css file will be loaded for all pages. When using a path to a file the `--assets-dir` switch must be used when generating the site and the corresponding file is available in the `assets` folder. | | `site/custom.css` or 'https://uri.example/custom.css | -| `generatr.search.language` | Indexing/stemming language for the search index. See [Lunr language support](https://github.com/olivernn/lunr-languages) | `en` | `nl` | -| `generatr.markdown.flexmark.extensions` | Additional extensions to the markdown generator to add new markdown capabilities. [More Details](https://avisi-cloud.github.io/structurizr-site-generatr/main/extended-markdown-features/) | Tables | `Tables,Admonition` | -| `generatr.svglink.target` | Specifies the link target for element links in the exported svg | `_top` | `_self` | -| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` | +| `generatr.search.language` | Indexing/stemming language for the search index. See [Lunr language support](https://github.com/olivernn/lunr-languages) | `en` | `nl` | +| `generatr.markdown.flexmark.extensions` | Additional extensions to the markdown generator to add new markdown capabilities. [More Details](https://avisi-cloud.github.io/structurizr-site-generatr/main/extended-markdown-features/) | Tables | `Tables,Admonition` | +| `generatr.svglink.target` | Specifies the link target for element links in the exported svg | `_top` | `_self` | +| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` | See the included example for usage of some those properties in the diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt index 08c3cd1c..971d9e39 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModel.kt @@ -57,5 +57,4 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode .sortedBy { it.lowercase() } private val delimiter = '/' - } diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt index 438e8970..8e58bdb0 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt @@ -69,6 +69,7 @@ private fun buildHtmlTree(node: MenuNodeViewModel, viewModel: MenuViewModel): UL } } } + if (node.name.isEmpty() && node.children.isNotEmpty()) { ul(classes = "listree-submenu-items") { for (child in node.children) { @@ -76,5 +77,4 @@ private fun buildHtmlTree(node: MenuNodeViewModel, viewModel: MenuViewModel): UL } } } - } diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt index 094dde89..4e329664 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt @@ -154,7 +154,6 @@ class MenuViewModelTest : ViewModelTest() { assertThat(it.softwareSystemNodes().children[0].children[0].children[0].name).isEqualTo("System 4") } - } private fun createPageViewModel(generatorContext: GeneratorContext, url: String = "/master/page"): PageViewModel { From ca8dab00b2e9a9a841b8620e7b53ccccfac125f6 Mon Sep 17 00:00:00 2001 From: Declan Lynch Date: Fri, 13 Oct 2023 10:17:56 -0400 Subject: [PATCH 8/8] Fix some additional whitespace issues --- .../kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt | 1 - .../structurizr/site/generatr/site/model/MenuViewModelTest.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt index 8e58bdb0..983e445f 100644 --- a/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt +++ b/src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Menu.kt @@ -40,7 +40,6 @@ private fun ASIDE.menuItemLinks(items: List) { } private fun buildHtmlTree(node: MenuNodeViewModel, viewModel: MenuViewModel): UL.() -> Unit = { - if (node.name.isNotEmpty() && node.children.isEmpty()) { val itemLink = viewModel.softwareSystemItems.find { it.title == node.name } li { diff --git a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt index 4e329664..5660784d 100644 --- a/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt +++ b/src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/MenuViewModelTest.kt @@ -153,7 +153,6 @@ class MenuViewModelTest : ViewModelTest() { assertThat(it.softwareSystemNodes().children[0].children[0].name).isEqualTo("Group 3") assertThat(it.softwareSystemNodes().children[0].children[0].children[0].name).isEqualTo("System 4") } - } private fun createPageViewModel(generatorContext: GeneratorContext, url: String = "/master/page"): PageViewModel {