Tue, 23 Jun 2026 14:30:10 +0200
add the possibility to hide projects from the left menu - resolves #818
--- a/build.gradle.kts Tue Jun 23 13:06:07 2026 +0200 +++ b/build.gradle.kts Tue Jun 23 14:30:10 2026 +0200 @@ -5,7 +5,7 @@ war } group = "de.uapcore" -version = "1.6.2" +version = "1.7.0" repositories { mavenCentral()
--- a/setup/postgres/psql_create_tables.sql Tue Jun 23 13:06:07 2026 +0200 +++ b/setup/postgres/psql_create_tables.sql Tue Jun 23 14:30:10 2026 +0200 @@ -16,6 +16,7 @@ name text not null unique, node text not null unique, ordinal integer not null default 0, + hidden boolean not null default false, description text, repoUrl text, vcs vcstype not null default 'None'::vcstype,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup/postgres/psql_patch_1.7.0.sql Tue Jun 23 14:30:10 2026 +0200 @@ -0,0 +1,3 @@ +-- apply this script to patch a version 1.6.x database to version 1.7.0 + +alter table lpit_project add column hidden boolean default false;
--- a/src/main/kotlin/de/uapcore/lightpit/Constants.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/Constants.kt Tue Jun 23 14:30:10 2026 +0200 @@ -29,7 +29,7 @@ /** * A date in yyyy-mm-dd format to identify the release. */ - const val VERSION_DATE = "2026-06-11" + const val VERSION_DATE = "2026-06-23" /** * The path where the JSP files reside. @@ -116,4 +116,9 @@ * Key for the current timezone selection within the session. */ const val SESSION_ATTR_TIMEZONE = "timezone" + + /** + * Key for the session attribute that controls whether all projects are shown in the navmenu. + */ + const val SESSION_ATTR_SHOW_ALL_PROJECTS = "show_all_projects" }
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Tue Jun 23 14:30:10 2026 +0200 @@ -48,7 +48,12 @@ val node = info.node } -sealed interface ValidationResult<T> +sealed interface ValidationResult<T> { + fun getOrNull(): T? = when (this) { + is ValidatedValue -> result + else -> null + } +} class ValidationError<T>(val message: String): ValidationResult<T> class ValidatedValue<T>(val result: T): ValidationResult<T>
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Tue Jun 23 14:30:10 2026 +0200 @@ -480,7 +480,7 @@ //language=SQL private val projectQuery = """ - select projectid, name, node, ordinal, description, vcs, repourl, + select projectid, name, node, ordinal, hidden, description, vcs, repourl, userid, username, lastname, givenname, mail from lpit_project left join lpit_user owner on lpit_project.owner = owner.userid @@ -491,6 +491,7 @@ name = getString("${prefix}name") node = getString("${prefix}node") ordinal = getInt("${prefix}ordinal") + hidden = getBoolean("${prefix}hidden") description = getString("${prefix}description") vcs = getEnum("${prefix}vcs") repoUrl = getString("${prefix}repourl") @@ -503,6 +504,7 @@ setStringSafe(i++, name) setStringSafe(i++, node) setInt(i++, ordinal) + setBoolean(i++, hidden) setStringOrNull(i++, description) setEnum(i++, vcs) setStringOrNull(i++, repoUrl) @@ -529,14 +531,14 @@ } override fun insertProject(project: Project) { - withStatement("insert into lpit_project (name, node, ordinal, description, vcs, repourl, owner) values (?, ?, ?, ?, ?::vcstype, ?, ?)") { + withStatement("insert into lpit_project (name, node, ordinal, hidden, description, vcs, repourl, owner) values (?, ?, ?, ?, ?::vcstype, ?, ?)") { setProject(1, project) executeUpdate() } } override fun updateProject(project: Project) { - withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, vcs = ?::vcstype, repourl = ?, owner = ? where projectid = ?") { + withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, hidden = ?, description = ?, vcs = ?::vcstype, repourl = ?, owner = ? where projectid = ?") { val col = setProject(1, project) setInt(col, project.id) executeUpdate() @@ -626,6 +628,7 @@ p.name as project_name, p.node as project_node, p.ordinal as project_ordinal, + p.hidden as project_hidden, p.description as project_description, p.vcs as project_vcs, p.repourl as project_repourl, @@ -648,6 +651,7 @@ p.name as project_name, p.node as project_node, p.ordinal as project_ordinal, + p.hidden as project_hidden, p.description as project_description, p.vcs as project_vcs, p.repourl as project_repourl,
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Tue Jun 23 14:30:10 2026 +0200 @@ -31,6 +31,7 @@ var name: String = "" override var node: String = name var ordinal = 0 + var hidden = false var description: String? = null var vcs: VcsType = VcsType.None var repoUrl: String? = null
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Tue Jun 23 14:30:10 2026 +0200 @@ -38,6 +38,7 @@ class ProjectServlet : AbstractServlet() { init { + post("/settings", this::changeSettings) get("/", this::projects) get("/%project", this::project) get("/%project/issues/%version/%component/%variant/", this::project) @@ -75,6 +76,13 @@ post("/%project/issues/%version/%component/%variant/-/commit", this::issueCommit) } + private fun changeSettings(http: HttpRequest, dao: DataAccessObject) { + http.param("show_all_projects")?.let(::boolValidator)?.getOrNull()?.let { + http.session.setAttribute(Constants.SESSION_ATTR_SHOW_ALL_PROJECTS, it) + } + http.response.status = 204 + } + private fun projects(http: HttpRequest, dao: DataAccessObject) { val projects = dao.listProjects() val projectInfos = projects.map { @@ -154,6 +162,7 @@ node = http.param("node") ?: "" description = http.param("description") ?: "" ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 + hidden = http.param("hidden", ::boolValidator, false, mutableListOf()) repoUrl = http.param("repoUrl") ?: "" vcs = VcsType.valueOf(http.param("vcs") ?: "None") owner = (http.param("owner")?.toIntOrNull() ?: -1).let { @@ -171,7 +180,11 @@ dao.updateProject(project) } - http.renderCommit("projects/${project.node}") + if (project.hidden) { + http.renderCommit("projects") + } else { + http.renderCommit("projects/${project.node}") + } } private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) { @@ -241,7 +254,6 @@ dao.listVersionSummaries(path.project) ) navigationMenu = projectNavMenu(dao.listProjects(), path) - javascript = "issue-overview" render("versions") } } @@ -350,7 +362,6 @@ dao.listComponentSummaries(path.project) ) navigationMenu = projectNavMenu(dao.listProjects(), path) - javascript = "issue-overview" render("components") } } @@ -381,7 +392,6 @@ ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 color = WebColor(http.param("color") ?: "#000000") description = http.param("description") - // TODO: process error message active = http.param("active", ::boolValidator, true, mutableListOf()) lead = (http.param("lead")?.toIntOrNull() ?: -1).let { if (it < 0) null else dao.findUser(it) @@ -410,7 +420,6 @@ dao.listVariantSummaries(path.project) ) navigationMenu = projectNavMenu(dao.listProjects(), path) - javascript = "issue-overview" render("variants") } }
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/NavMenus.kt Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/NavMenus.kt Tue Jun 23 14:30:10 2026 +0200 @@ -34,6 +34,7 @@ val href: String, val title: String = "", val active: Boolean = false, + val hidden: Boolean = false, val resolveCaption: Boolean = false, val iconColor: String? = null, val editHref: String? = null @@ -50,6 +51,7 @@ NavMenuEntry( level = 0, caption = project.name, + hidden = project.hidden, href = "projects/${project.node}", ) ) @@ -68,6 +70,7 @@ NavMenuEntry( level = 0, caption = project.name, + hidden = !active && project.hidden, href = "projects/${project.node}", active = active )
--- a/src/main/resources/localization/strings.properties Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/resources/localization/strings.properties Tue Jun 23 14:30:10 2026 +0200 @@ -158,6 +158,7 @@ menu.projects=Projects menu.settings=Settings menu.users=Developer +navmenu.showall=Show All navmenu.all=all navmenu.components=Components navmenu.none=none @@ -178,6 +179,8 @@ placeholder.null-version=None placeholder.null-eta=None progress=Overall Progress +project.hidden=Hidden +project.hidden.tooltip=Hide this project from the left menu. project.name=Name project.owner=Project Lead project.repoUrl=Repository
--- a/src/main/resources/localization/strings_de.properties Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/resources/localization/strings_de.properties Tue Jun 23 14:30:10 2026 +0200 @@ -158,6 +158,7 @@ menu.projects=Projekte menu.settings=Einstellungen menu.users=Entwickler +navmenu.showall=Alle anzeigen navmenu.all=Alle navmenu.components=Komponenten navmenu.none=Keine @@ -178,6 +179,8 @@ placeholder.null-version=Keine placeholder.null-eta=Nicht geplant progress=Gesamtfortschritt +project.hidden=Versteckt +project.hidden.tooltip=Dieses Projekt wird im Navigationsmen\u00fc versteckt. project.name=Name project.owner=Projektleitung project.repoUrl=Repository
--- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Tue Jun 23 14:30:10 2026 +0200 @@ -24,6 +24,12 @@ --%> <%@ page contentType="text/html;charset=UTF-8" %> +<h3>Version 1.7.0 (Vorschau)</h3> + +<ul> + <li>Die Möglichkeit hinzugefügt, Projekte im Navigationsmenü zu verstecken.</li> +</ul> + <h3>Version 1.6.2</h3> <ul>
--- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Tue Jun 23 14:30:10 2026 +0200 @@ -24,6 +24,12 @@ --%> <%@ page contentType="text/html;charset=UTF-8" %> +<h3>Version 1.7.0 (preview)</h3> + +<ul> + <li>Add the possibility to hide projects from the left menu.</li> +</ul> + <h3>Version 1.6.2</h3> <ul>
--- a/src/main/webapp/WEB-INF/jsp/project-form.jsp Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-form.jsp Tue Jun 23 14:30:10 2026 +0200 @@ -84,6 +84,12 @@ <input name="ordinal" type="number" value="${project.ordinal}"/> </td> </tr> + <tr title="<fmt:message key="project.hidden.tooltip" />"> + <th><fmt:message key="project.hidden"/></th> + <td> + <input name="hidden" type="checkbox" <c:if test="${project.hidden}">checked</c:if>/> + </td> + </tr> </tbody> <tfoot> <tr>
--- a/src/main/webapp/WEB-INF/jsp/site.jsp Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/webapp/WEB-INF/jsp/site.jsp Tue Jun 23 14:30:10 2026 +0200 @@ -57,6 +57,9 @@ <%-- Define an alias for timezone --%> <c:set scope="page" var="timezone" value="${sessionScope[Constants.SESSION_ATTR_TIMEZONE]}" /> +<%-- Define an alias for project_showall --%> +<c:set scope="page" var="show_all_projects" value="${sessionScope[Constants.SESSION_ATTR_SHOW_ALL_PROJECTS]}" /> + <%-- Load resource bundle --%> <fmt:setLocale scope="request" value="${pageContext.response.locale}"/> <fmt:setBundle scope="request" basename="localization.strings"/> @@ -79,16 +82,8 @@ </c:if> <script> const baseHref='${baseHref}'; - <c:if test="${showWhatsNew}"> - function closeWhatsNew(showMore) { - if (showMore) { - window.open(baseHref + 'about', '_blank'); - } - document.getElementById('whats-new').style.display = 'none'; - document.getElementById('page-area').classList.remove('blurred'); - } - </c:if> </script> + <script src="lightpit.js?v=${versionSuffix}" type="text/javascript"></script> <script src="issue-search.js?v=${versionSuffix}" type="text/javascript"></script> <c:if test="${not empty javascriptFile}"> <script src="${javascriptFile}?v=${versionSuffix}" type="text/javascript"></script>
--- a/src/main/webapp/WEB-INF/jspf/navmenu.jspf Tue Jun 23 13:06:07 2026 +0200 +++ b/src/main/webapp/WEB-INF/jspf/navmenu.jspf Tue Jun 23 14:30:10 2026 +0200 @@ -30,9 +30,14 @@ <jsp:useBean id="navMenu" type="de.uapcore.lightpit.viewmodel.NavMenu" scope="request" /> +<div class="menuEntry level-0"> + <input id="show-all-projects" type="checkbox" <c:if test="${show_all_projects}">checked</c:if> onclick="toggleShowAllProjects()"/> + <label for="show-all-projects"><fmt:message key="navmenu.showall"/> </label> +</div> <c:forEach var="entry" items="${navMenu.entries}"> <div class="menuEntry level-${entry.level}" <c:if test="${entry.active}"> data-active </c:if> + <c:if test="${entry.hidden}"> data-hidden </c:if> <c:if test="${not empty entry.title}">title="<fmt:message key="${entry.title}"/>" </c:if> > <c:if test="${not empty entry.iconColor}">
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/webapp/lightpit.js Tue Jun 23 14:30:10 2026 +0200 @@ -0,0 +1,55 @@ +/* + * Copyright 2025 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +function closeWhatsNew(showMore) { + if (showMore) { + window.open(baseHref + 'about', '_blank'); + } + document.getElementById('whats-new').style.display = 'none'; + document.getElementById('page-area').classList.remove('blurred'); +} + +function applyShowAllProjects() { + const toggle = document.getElementById('show-all-projects'); + if (!toggle) return; // this page does not show the navmenu + const checked = toggle.checked; + document.querySelectorAll('#sideMenu .menuEntry[data-hidden]').forEach((elem) => { + elem.style.display = checked ? 'initial' : 'none'; + }) +} + +function toggleShowAllProjects() { + const toggle = document.getElementById('show-all-projects'); + + const req = new XMLHttpRequest(); + req.open('POST', baseHref+'projects/settings'); + req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + req.send('show_all_projects=' + (toggle.checked ? 'true' : 'false')); + applyShowAllProjects(); +} + +window.addEventListener('load', () => { + applyShowAllProjects(); +});