I2PSnark UI bugfixes

- Fix broken collapsible panels issue for browsers that don't support the
  feature by conditionally loading override CSS to expand panels by default
  and disable hover/active states for panel headings (tickets #2002, #2026)
- Add UI option to configuration section to enable collapsible panels, and
  disable the option if a non-compliant browser is detected
- Fix multiple instances of snark refreshing to the homepage (ticket #2028)
  (patch supplied by mindless)
- Tentative fix for caching of images so ajax refresh doesn't reload all
  image resources
- Standardize 'Save Configuration' action to return to top of the page
  (so we can see message log entry)
This commit is contained in:
str4d
2017-10-25 09:35:51 +00:00
parent 47d354711e
commit df95e29f4e
14 changed files with 436 additions and 35 deletions

View File

@ -65,6 +65,7 @@ public class I2PSnarkUtil {
private int _maxConnections;
private final File _tmpDir;
private int _startupDelay;
private boolean _collapsePanels;
private boolean _shouldUseOT;
private boolean _shouldUseDHT;
private boolean _enableRatings, _enableComments;
@ -77,6 +78,7 @@ public class I2PSnarkUtil {
private static final int EEPGET_CONNECT_TIMEOUT = 45*1000;
private static final int EEPGET_CONNECT_TIMEOUT_SHORT = 5*1000;
public static final int DEFAULT_STARTUP_DELAY = 3;
public static final boolean DEFAULT_COLLAPSE_PANELS = true;
public static final boolean DEFAULT_USE_OPENTRACKERS = true;
public static final int MAX_CONNECTIONS = 24; // per torrent
public static final String PROP_MAX_BW = "i2cp.outboundBytesPerSecond";
@ -106,6 +108,7 @@ public class I2PSnarkUtil {
_shouldUseOT = DEFAULT_USE_OPENTRACKERS;
_openTrackers = Collections.emptyList();
_shouldUseDHT = DEFAULT_USE_DHT;
_collapsePanels = DEFAULT_COLLAPSE_PANELS;
_enableRatings = _enableComments = true;
_commentsName = "";
// This is used for both announce replies and .torrent file downloads,
@ -687,6 +690,16 @@ public class I2PSnarkUtil {
return _enableRatings || _enableComments;
}
/** @since 0.9.32 */
public boolean collapsePanels() {
return _collapsePanels;
}
/** @since 0.9.32 */
public void setCollapsePanels(boolean yes) {
_collapsePanels = yes;
}
/**
* Like DataHelper.toHexString but ensures no loss of leading zero bytes
* @since 0.8.4

View File

@ -131,6 +131,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
public static final String RC_PROP_UNIVERSAL_THEMING = "routerconsole.universal.theme";
public static final String PROP_THEME = "i2psnark.theme";
public static final String DEFAULT_THEME = "ubergine";
/** @since 0.9.32 */
public static final String PROP_COLLAPSE_PANELS = "i2psnark.collapsePanels";
private static final String PROP_USE_OPENTRACKERS = "i2psnark.useOpentrackers";
public static final String PROP_OPENTRACKERS = "i2psnark.opentrackers";
public static final String PROP_PRIVATETRACKERS = "i2psnark.privatetrackers";
@ -454,6 +456,17 @@ public class SnarkManager implements CompleteListener, ClientApp {
return Boolean.parseBoolean(val);
}
/**
* @return default true
* @since 0.9.32
*/
public boolean isCollapsePanelsEnabled() {
String val = _config.getProperty(PROP_COLLAPSE_PANELS);
if (val == null)
return I2PSnarkUtil.DEFAULT_COLLAPSE_PANELS;
return Boolean.parseBoolean(val);
}
/****
public String linkPrefix() {
return _config.getProperty(PROP_LINK_PREFIX, DEFAULT_LINK_PREFIX + getDataDir().getAbsolutePath() + File.separatorChar);
@ -798,6 +811,9 @@ public class SnarkManager implements CompleteListener, ClientApp {
_config.setProperty(PROP_COMMENTS, "true");
if (!_config.containsKey(PROP_COMMENTS_NAME))
_config.setProperty(PROP_COMMENTS_NAME, "");
if (!_config.containsKey(PROP_COLLAPSE_PANELS))
_config.setProperty(PROP_COLLAPSE_PANELS,
Boolean.toString(I2PSnarkUtil.DEFAULT_COLLAPSE_PANELS));
updateConfig();
}
@ -871,7 +887,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
_util.setMaxUpBW(limits[1]);
}
}
private void updateConfig() {
String i2cpHost = _config.getProperty(PROP_I2CP_HOST);
int i2cpPort = getInt(PROP_I2CP_PORT, 7654);
@ -908,6 +924,8 @@ public class SnarkManager implements CompleteListener, ClientApp {
_util.setRatingsEnabled(Boolean.parseBoolean(_config.getProperty(PROP_RATINGS, "true")));
_util.setCommentsEnabled(Boolean.parseBoolean(_config.getProperty(PROP_COMMENTS, "true")));
_util.setCommentsName(_config.getProperty(PROP_COMMENTS_NAME, ""));
_util.setCollapsePanels(Boolean.parseBoolean(_config.getProperty(PROP_COLLAPSE_PANELS,
Boolean.toString(I2PSnarkUtil.DEFAULT_COLLAPSE_PANELS))));
File dd = getDataDir();
if (dd.isDirectory()) {
if (!dd.canWrite())
@ -918,7 +936,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
}
initTrackerMap();
}
private int getInt(String prop, int defaultVal) {
String p = _config.getProperty(prop);
try {
@ -929,29 +947,29 @@ public class SnarkManager implements CompleteListener, ClientApp {
}
return defaultVal;
}
/**
* all params may be null or need trimming
*/
public void updateConfig(String dataDir, boolean filesPublic, boolean autoStart, boolean smartSort, String refreshDelay,
String startDelay, String pageSize, String seedPct, String eepHost,
String startDelay, String pageSize, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme,
String lang, boolean enableRatings, boolean enableComments, String commentName) {
String lang, boolean enableRatings, boolean enableComments, String commentName, boolean collapsePanels) {
synchronized(_configLock) {
locked_updateConfig(dataDir, filesPublic, autoStart, smartSort,refreshDelay,
startDelay, pageSize, seedPct, eepHost,
eepPort, i2cpHost, i2cpPort, i2cpOpts,
upLimit, upBW, useOpenTrackers, useDHT, theme,
lang, enableRatings, enableComments, commentName);
locked_updateConfig(dataDir, filesPublic, autoStart, smartSort, refreshDelay,
startDelay, pageSize, seedPct, eepHost,
eepPort, i2cpHost, i2cpPort, i2cpOpts,
upLimit, upBW, useOpenTrackers, useDHT, theme,
lang, enableRatings, enableComments, commentName, collapsePanels);
}
}
private void locked_updateConfig(String dataDir, boolean filesPublic, boolean autoStart, boolean smartSort, String refreshDelay,
String startDelay, String pageSize, String seedPct, String eepHost,
String startDelay, String pageSize, String seedPct, String eepHost,
String eepPort, String i2cpHost, String i2cpPort, String i2cpOpts,
String upLimit, String upBW, boolean useOpenTrackers, boolean useDHT, String theme,
String lang, boolean enableRatings, boolean enableComments, String commentName) {
String lang, boolean enableRatings, boolean enableComments, String commentName, boolean collapsePanels) {
boolean changed = false;
boolean interruptMonitor = false;
//if (eepHost != null) {
@ -996,7 +1014,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
}
}
}
if (startDelay != null && _context.isRouterContext()) {
int minutes = _util.getStartupDelay();
try { minutes = Integer.parseInt(startDelay.trim()); } catch (NumberFormatException nfe) {}
@ -1115,7 +1133,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
if (split > 0)
oldOpts.put(pair.substring(0, split), pair.substring(split+1));
}
boolean reconnect = i2cpHost != null && i2cpHost.trim().length() > 0 && port > 0 &&
(port != _util.getI2CPPort() || !oldI2CPHost.equals(i2cpHost));
if (reconnect || !oldOpts.equals(opts)) {
@ -1262,6 +1280,15 @@ public class SnarkManager implements CompleteListener, ClientApp {
changed = true;
}
}
if (_util.collapsePanels() != collapsePanels) {
_config.setProperty(PROP_COLLAPSE_PANELS, Boolean.toString(collapsePanels));
if (collapsePanels)
addMessage(_t("Collapsible panels enabled."));
else
addMessage(_t("Collapsible panels disabled."));
_util.setCollapsePanels(collapsePanels);
changed = true;
}
if (changed) {
saveConfig();
if (interruptMonitor)
@ -1485,7 +1512,7 @@ public class SnarkManager implements CompleteListener, ClientApp {
fis.close();
fis = null;
} catch (IOException e) {}
// This test may be a duplicate, but not if we were called
// from the DirMonitor, which only checks for dup torrent file names.
Snark snark = getTorrentByInfoHash(info.getInfoHash());

View File

@ -268,6 +268,9 @@ public class I2PSnarkServlet extends BasicServlet {
return;
}
boolean noCollapse = noCollapsePanels(req);
boolean collapsePanels = _manager.util().collapsePanels();
setHTMLHeaders(resp);
PrintWriter out = resp.getWriter();
out.write(DOCTYPE + "<html>\n" +
@ -295,7 +298,7 @@ public class I2PSnarkServlet extends BasicServlet {
String jsPfx = _context.isRouterContext() ? "" : ".resources";
String downMsg = _context.isRouterContext() ? _t("Router is down") : _t("I2PSnark has stopped");
// fallback to metarefresh when javascript is disabled
out.write("<noscript><meta http-equiv=\"refresh\" content=\"" + delay + ";/i2psnark/" + peerString + "\"></noscript>\n");
out.write("<noscript><meta http-equiv=\"refresh\" content=\"" + delay + ";" + _contextPath + "/" + peerString + "\"></noscript>\n");
out.write("<script src=\"" + jsPfx + "/js/ajax.js\" type=\"text/javascript\"></script>\n" +
"<script type=\"text/javascript\">\n" +
"var failMessage = \"<div class=\\\"routerdown\\\"><b>" + downMsg + "<\\/b><\\/div>\";\n" +
@ -306,7 +309,14 @@ public class I2PSnarkServlet extends BasicServlet {
"</script>\n");
}
}
out.write(HEADER_A + _themePath + HEADER_B + "</head>\n");
out.write(HEADER_A + _themePath + HEADER_B);
// ...and inject CSS to display panels uncollapsed
if (noCollapse || !collapsePanels) {
out.write(HEADER_A + _themePath + HEADER_C);
}
out.write("</head>\n");
if (isConfigure || delay <= 0)
out.write("<body>");
else
@ -381,9 +391,10 @@ public class I2PSnarkServlet extends BasicServlet {
private static void setHTMLHeaders(HttpServletResponse resp) {
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/html; charset=UTF-8");
resp.setHeader("Cache-Control", "no-store, max-age=0, no-cache, must-revalidate");
// "no-store, max-age=0" forces all our images to be reloaded on ajax refresh
resp.setHeader("Cache-Control", "max-age=86400, no-cache, must-revalidate");
resp.setHeader("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'");
resp.setDateHeader("Expires", 0);
resp.setDateHeader("Expires", 86400);
resp.setHeader("Pragma", "no-cache");
resp.setHeader("X-Frame-Options", "SAMEORIGIN");
resp.setHeader("X-XSS-Protection", "1; mode=block");
@ -1190,10 +1201,11 @@ public class I2PSnarkServlet extends BasicServlet {
boolean comments = req.getParameter("comments") != null;
// commentsName is filtered in SnarkManager.updateConfig()
String commentsName = req.getParameter("nofilter_commentsName");
boolean collapsePanels = req.getParameter("collapsePanels") != null;
_manager.updateConfig(dataDir, filesPublic, autoStart, smartSort, refreshDel, startupDel, pageSize,
seedPct, eepHost, eepPort, i2cpHost, i2cpPort, i2cpOpts,
upLimit, upBW, useOpenTrackers, useDHT, theme,
lang, ratings, comments, commentsName);
lang, ratings, comments, commentsName, collapsePanels);
// update servlet
try {
setResourceBase(_manager.getDataDir());
@ -2271,9 +2283,12 @@ public class I2PSnarkServlet extends BasicServlet {
boolean useDHT = _manager.util().shouldUseDHT();
boolean useRatings = _manager.util().ratingsEnabled();
boolean useComments = _manager.util().commentsEnabled();
boolean collapsePanels = _manager.util().collapsePanels();
//int seedPct = 0;
out.write("<form action=\"" + _contextPath + "/configure\" method=\"POST\">\n" +
boolean noCollapse = noCollapsePanels(req);
out.write("<form action=\"" + _contextPath + "/configure\" method=\"POST\" target=\"_top\">\n" +
"<div class=\"configsectionpanel\"><div class=\"snarkConfig\">\n");
writeHiddenInputs(out, req, "Save");
out.write("<span class=\"snarkConfigTitle\">");
@ -2309,10 +2324,23 @@ public class I2PSnarkServlet extends BasicServlet {
"<tr><td><label for=\"smartSort\">");
out.write(_t("Smart torrent sorting"));
out.write(":</label><td colspan=\"2\"><input type=\"checkbox\" class=\"optbox\" name=\"smartSort\" id=\"smartSort\" value=\"true\" "
+ (smartSort ? "checked " : "")
+ (smartSort ? "checked " : "")
+ "title=\"");
out.write(_t("Ignore words such as 'a' and 'the' when sorting"));
out.write("\" >");
out.write("\" >" +
"<tr><td><label for=\"collapsePanels\">");
out.write(_t("Collapsible panels"));
out.write(":</label><td colspan=\"2\"><input type=\"checkbox\" class=\"optbox\" name=\"collapsePanels\" id=\"collapsePanels\" value=\"true\" "
+ (collapsePanels ? "checked " : "")
+ "title=\"");
if (noCollapse) {
out.write(_t("Your browser does not support this feature."));
out.write("\" disabled=\"disabled");
} else {
out.write(_t("Allow the 'Add Torrent' and 'Create Torrent' panels to be collapsed, and collapse by default in non-embedded mode"));
}
out.write("\">");
if (!_context.isRouterContext()) {
try {
@ -2744,7 +2772,7 @@ public class I2PSnarkServlet extends BasicServlet {
buf.append("<a href=\"").append(link).append("\">").append(display).append("</a>");
return buf.toString();
}
/**
* This is for a full URL. For a path only, use encodePath().
* @since 0.8.13
@ -2758,7 +2786,7 @@ public class I2PSnarkServlet extends BasicServlet {
private static final String DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
private static final String HEADER_A = "<link href=\"";
private static final String HEADER_B = "snark.css?" + CoreVersion.VERSION + "\" rel=\"stylesheet\" type=\"text/css\" >";
private static final String HEADER_C = "nocollapse.css?" + CoreVersion.VERSION + "\" rel=\"stylesheet\" type=\"text/css\" >";
private static final String TABLE_HEADER = "<table border=\"0\" class=\"snarkTorrents\" width=\"100%\" >\n" +
"<thead>\n";
@ -3851,6 +3879,14 @@ public class I2PSnarkServlet extends BasicServlet {
_manager.setSavedCommentsEnabled(snark, yes);
}
private static boolean noCollapsePanels(HttpServletRequest req) {
// check for user agents that can't toggle the collapsible panels...
String ua = req.getHeader("user-agent");
return ua != null && (ua.contains("Konq") || ua.contains("konq") ||
ua.contains("Qupzilla") || ua.contains("Dillo") ||
ua.contains("Netsurf") || ua.contains("Midori"));
}
/**
* Is "a" equal to "b",
* or is "a" a directory and a parent of file or directory "b",