From 282460cb3f5f331303b05ceb8b1981477ebfd3e9 Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Thu, 30 Sep 2021 09:55:35 -0400 Subject: [PATCH] Console: Add js to /configui to preview themes Save theme change before form processing so no refresh required Enable/disable reset and apply buttons on config clicks Prep for theme picker in wizard --- .../src/net/i2p/router/web/CSSHelper.java | 10 +++ .../router/web/helpers/ConfigUIHandler.java | 35 +++++----- .../router/web/helpers/ConfigUIHelper.java | 6 +- apps/routerconsole/jsp/configui.jsp | 9 +-- apps/routerconsole/jsp/css.jsi | 12 ++-- apps/routerconsole/jsp/js/configui.js | 67 +++++++++++++++++++ apps/routerconsole/jsp/summarynoframe.jsi | 2 +- 7 files changed, 110 insertions(+), 31 deletions(-) create mode 100644 apps/routerconsole/jsp/js/configui.js diff --git a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java index 2926d5d0e1..308daa481d 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/CSSHelper.java @@ -69,6 +69,16 @@ public class CSSHelper extends HelperBase { return url; } + /** + * So we don't have to refresh after saving. Called from css.jsi. + * @since 0.9.52 + */ + public void setTheme(String theme) { + if (theme != null && theme.length() > 0 && + theme.replaceAll("[a-zA-Z0-9_-]", "").length() == 0) + _context.router().saveConfig(PROP_THEME_NAME, theme); + } + /** * Returns whether app embedding is enabled or disabled * @since 0.9.32 diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHandler.java index c1d0505aa3..219bd38352 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHandler.java @@ -28,7 +28,7 @@ public class ConfigUIHandler extends FormHandler { delUser(); } else if (_action.equals(_t("Add user"))) { addUser(); - } + } // else lang change, handled in CSSHelper } public void setShouldsave(String moo) { _shouldSave = true; } @@ -43,22 +43,23 @@ public class ConfigUIHandler extends FormHandler { _config = val; } - /** note - lang change is handled in CSSHelper but we still need to save it here */ + /** + * This is for the theme options only. + * Lang change is handled in CSSHelper. + */ private void saveChanges() { - if (_config == null || _config.length() <= 0) - return; - if (_config.replaceAll("[a-zA-Z0-9_-]", "").length() != 0) { - addFormError("Bad theme name"); - return; - } Map<String, String> changes = new HashMap<String, String>(); List<String> removes = new ArrayList<String>(); String oldTheme = _context.getProperty(CSSHelper.PROP_THEME_NAME, CSSHelper.DEFAULT_THEME); boolean oldForceMobileConsole = _context.getBooleanProperty(CSSHelper.PROP_FORCE_MOBILE_CONSOLE); - if (_config.equals("default")) // obsolete - removes.add(CSSHelper.PROP_THEME_NAME); - else - changes.put(CSSHelper.PROP_THEME_NAME, _config); + boolean validTheme = _config != null && _config.length() > 0 && + _config.replaceAll("[a-zA-Z0-9_-]", "").length() == 0; + if (validTheme) { + if (_config.equals("default")) // obsolete + removes.add(CSSHelper.PROP_THEME_NAME); + else + changes.put(CSSHelper.PROP_THEME_NAME, _config); + } if (_universalTheming) changes.put(CSSHelper.PROP_UNIVERSAL_THEMING, "true"); else @@ -73,11 +74,11 @@ public class ConfigUIHandler extends FormHandler { removes.add(CSSHelper.PROP_EMBED_APPS); boolean ok = _context.router().saveConfig(changes, removes); if (ok) { - if (!oldTheme.equals(_config)) - addFormNoticeNoEscape(_t("Theme change saved.") + - " <a href=\"configui\">" + - _t("Refresh the page to view.") + - "</a>"); + // Theme saving happens in CSSHelper + // so output this even if it didn't change + //if (!oldTheme.equals(_config)) + if (validTheme) + addFormNotice(_t("Theme change saved.")); if (oldForceMobileConsole != _forceMobileConsole) addFormNoticeNoEscape(_t("Mobile console option saved.") + " <a href=\"configui\">" + diff --git a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java index 49b5df0cc5..cae5214718 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/helpers/ConfigUIHelper.java @@ -41,7 +41,7 @@ public class ConfigUIHelper extends HelperBase { } boolean universalTheming = _context.getBooleanProperty(CSSHelper.PROP_UNIVERSAL_THEMING); buf.append("</div><div id=\"themeoptions\">" + - "<label><input type=\"checkbox\" name=\"universalTheming\" "); + "<label><input id=\"themebox1\" type=\"checkbox\" name=\"universalTheming\" "); if (universalTheming) buf.append(CHECKED); buf.append("value=\"1\">") @@ -53,7 +53,7 @@ public class ConfigUIHelper extends HelperBase { public String getForceMobileConsole() { StringBuilder buf = new StringBuilder(256); boolean forceMobileConsole = _context.getBooleanProperty(CSSHelper.PROP_FORCE_MOBILE_CONSOLE); - buf.append("<label><input type=\"checkbox\" name=\"forceMobileConsole\" "); + buf.append("<label><input id=\"themebox2\" type=\"checkbox\" name=\"forceMobileConsole\" "); if (forceMobileConsole) buf.append(CHECKED); buf.append("value=\"1\">") @@ -62,7 +62,7 @@ public class ConfigUIHelper extends HelperBase { boolean embedApps = _context.getBooleanProperty(CSSHelper.PROP_EMBED_APPS); buf.append("<label title=\"") .append(_t("Enabling the Universal Theming option is recommended when embedding these applications")) - .append("\"><input type=\"checkbox\" name=\"embedApps\" "); + .append("\"><input id=\"themebox3\" type=\"checkbox\" name=\"embedApps\" "); if (embedApps) buf.append(CHECKED); buf.append("value=\"1\">") diff --git a/apps/routerconsole/jsp/configui.jsp b/apps/routerconsole/jsp/configui.jsp index 06fafe3df1..58bb1b233d 100644 --- a/apps/routerconsole/jsp/configui.jsp +++ b/apps/routerconsole/jsp/configui.jsp @@ -13,6 +13,7 @@ input.default { } </style> <%@include file="summaryajax.jsi" %> +<script src="/js/configui.js?<%=net.i2p.CoreVersion.VERSION%>" type="text/javascript"></script> </head><body> <%@include file="summary.jsi" %> <jsp:useBean class="net.i2p.router.web.helpers.ConfigUIHelper" id="uihelper" scope="request" /> @@ -26,7 +27,7 @@ input.default { <jsp:useBean class="net.i2p.router.web.helpers.ConfigUIHandler" id="formhandler" scope="request" /> <%@include file="formhandler.jsi" %> <h3 id="themeheading"><%=uihelper._t("Router Console Theme")%></h3> - <form action="" method="POST"> + <form id="themeForm" action="" method="POST"> <input type="hidden" name="consoleNonce" value="<%=net.i2p.router.web.CSSHelper.getNonce()%>" > <input type="hidden" name="nonce" value="<%=pageNonce%>" > <input type="hidden" name="action" value="blah" > @@ -43,8 +44,8 @@ input.default { <% } %> <jsp:getProperty name="uihelper" property="forceMobileConsole" /> <hr><div class="formaction" id="themeui"> -<input type="reset" class="cancel" value="<%=intl._t("Cancel")%>" > -<input type="submit" name="shouldsave" class="accept" value="<%=intl._t("Apply")%>" > +<input id="themeCancel" type="reset" class="cancel" value="<%=intl._t("Cancel")%>" > +<input id="themeApply" type="submit" name="shouldsave" class="accept" value="<%=intl._t("Apply")%>" > </div></div></form> <h3 id="langheading"><%=uihelper._t("Router Console Language")%></h3> <form action="" method="POST"> @@ -56,7 +57,7 @@ input.default { <p id="helptranslate"><%=uihelper._t("Please contribute to the router console translation project! Contact the developers in #i2p-dev on IRC to help.")%> </p><hr><div class="formaction" id="langui"> <input type="reset" class="cancel" value="<%=intl._t("Cancel")%>" > -<input type="submit" name="shouldsave" class="accept" value="<%=intl._t("Apply")%>" > +<input type="submit" name="foo" class="accept" value="<%=intl._t("Apply")%>" > </div></div></form> <h3 id="passwordheading"><%=uihelper._t("Router Console Password")%></h3> diff --git a/apps/routerconsole/jsp/css.jsi b/apps/routerconsole/jsp/css.jsi index aad30f9b97..e3ab1970b7 100644 --- a/apps/routerconsole/jsp/css.jsi +++ b/apps/routerconsole/jsp/css.jsi @@ -33,11 +33,6 @@ <jsp:useBean class="net.i2p.router.web.CSSHelper" id="intl" scope="request" /> <jsp:setProperty name="intl" property="contextId" value="<%=i2pcontextId%>" /><% - // used several times below - String theUserAgent = request.getHeader("User-Agent"); - String theThemePath = intl.getTheme(theUserAgent); - -%><link rel="icon" href="<%=theThemePath%>images/favicon.ico"><% response.setHeader("Accept-Ranges", "none"); String cspNonce = Integer.toHexString(net.i2p.util.RandomSource.getInstance().nextInt()); @@ -61,8 +56,13 @@ if (net.i2p.router.web.CSSHelper.getNonce().equals(conNonceParam)) { intl.setLang(request.getParameter("lang")); intl.setNews(request.getParameter("news")); + intl.setTheme(request.getParameter("theme")); } -%><link href="<%=theThemePath%>console.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css"> + // used several times below + String theUserAgent = request.getHeader("User-Agent"); + String theThemePath = intl.getTheme(theUserAgent); +%><link rel="icon" href="<%=theThemePath%>images/favicon.ico"> +<link id="pagestyle" href="<%=theThemePath%>console.css?<%=net.i2p.CoreVersion.VERSION%>" rel="stylesheet" type="text/css"> <% if (intl.getLang().equals("zh")) { // make the fonts bigger for chinese diff --git a/apps/routerconsole/jsp/js/configui.js b/apps/routerconsole/jsp/js/configui.js new file mode 100644 index 0000000000..c1baa6c45c --- /dev/null +++ b/apps/routerconsole/jsp/js/configui.js @@ -0,0 +1,67 @@ +/* @license http://creativecommons.org/publicdomain/zero/1.0/legalcode CC0-1.0 */ + +// This component is dedicated to the public domain. It uses the CC0 +// as a formal dedication to the public domain and in circumstances where +// a public domain is not usable. + +var prefersDarkTheme = false; +var oldTheme = "light"; + +function detectDark() { + // https://stackoverflow.com/questions/56393880/how-do-i-detect-dark-mode-using-javascript + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + // dark mode + prefersDarkTheme = true; + } +} + +function swapStyleSheet(theme) { + // https://stackoverflow.com/questions/14292997/changing-style-sheet-javascript + document.getElementById("pagestyle").setAttribute("href", "/themes/console/" + theme + "/console.css"); + document.getElementById("i2plogo").setAttribute("src", "/themes/console/" + theme + "/images/i2plogo.png"); +} + +function disableButtons(disabled) { + document.getElementById("themeApply").disabled = disabled; + document.getElementById("themeCancel").disabled = disabled; +} + +function resetStyleSheet() { + swapStyleSheet(oldTheme); + document.getElementById("themeForm").reset(); + disableButtons(true); +} + +function initThemeSwitcher() { + var dark = document.getElementById("dark"); + dark.onclick = function() { + swapStyleSheet("dark"); + disableButtons(false); + } + if (dark.checked) { + oldTheme = "dark"; + } + var light = document.getElementById("light"); + light.onclick = function() { + swapStyleSheet("light"); + disableButtons(false); + } + var apply = document.getElementById("themeApply"); + apply.setAttribute("disabled", true); + var cancel = document.getElementById("themeCancel"); + cancel.setAttribute("disabled", true); + cancel.onclick = function() { + resetStyleSheet(); + } + document.getElementById("themebox1").onclick = function() { disableButtons(false); } + document.getElementById("themebox2").onclick = function() { disableButtons(false); } + document.getElementById("themebox3").onclick = function() { disableButtons(false); } + // unused for now + detectDark(); +} + +document.addEventListener("DOMContentLoaded", function() { + initThemeSwitcher(); +}, true); + +/* @license-end */ diff --git a/apps/routerconsole/jsp/summarynoframe.jsi b/apps/routerconsole/jsp/summarynoframe.jsi index 22c85834e9..8bdf2cc64a 100644 --- a/apps/routerconsole/jsp/summarynoframe.jsi +++ b/apps/routerconsole/jsp/summarynoframe.jsi @@ -8,7 +8,7 @@ %> <div> <a href="/" target="_top"> - <img src="<%=intl.getTheme(request.getHeader("User-Agent"))%>images/i2plogo.png" alt="<%=intl._t("I2P Router Console")%>" title="<%=intl._t("I2P Router Console")%>"> + <img id="i2plogo" src="<%=intl.getTheme(request.getHeader("User-Agent"))%>images/i2plogo.png" alt="<%=intl._t("I2P Router Console")%>" title="<%=intl._t("I2P Router Console")%>"> </a> </div> <div id="xhr"> -- GitLab