Commit 576bce8b authored by Matija Obreza's avatar Matija Obreza
Browse files

Explore: /explore replaces /acn

parent ef2fbb13
...@@ -19,9 +19,12 @@ package org.genesys2.server.servlet.controller; ...@@ -19,9 +19,12 @@ package org.genesys2.server.servlet.controller;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.ParameterCategory; import org.genesys2.server.model.genesys.ParameterCategory;
import org.genesys2.server.model.impl.Crop; import org.genesys2.server.model.impl.Crop;
import org.genesys2.server.service.CropService; import org.genesys2.server.service.CropService;
...@@ -32,6 +35,9 @@ import org.genesys2.server.service.TaxonomyService; ...@@ -32,6 +35,9 @@ import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.TraitService; import org.genesys2.server.service.TraitService;
import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue; import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
...@@ -46,7 +52,6 @@ import com.fasterxml.jackson.databind.JsonNode; ...@@ -46,7 +52,6 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@Controller @Controller
@RequestMapping("/explore")
public class ExplorerController extends BaseController { public class ExplorerController extends BaseController {
@Autowired @Autowired
...@@ -69,7 +74,7 @@ public class ExplorerController extends BaseController { ...@@ -69,7 +74,7 @@ public class ExplorerController extends BaseController {
private ObjectMapper mapper = new ObjectMapper(); private ObjectMapper mapper = new ObjectMapper();
@RequestMapping(value = "/pick", method = RequestMethod.GET) @RequestMapping(value = "/explore/pick", method = RequestMethod.GET)
public String pickFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter, public String pickFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "pick", required = false, defaultValue = "[]") String pick, @RequestParam(value = "crop", required = false) String shortName) { @RequestParam(value = "pick", required = false, defaultValue = "[]") String pick, @RequestParam(value = "crop", required = false) String shortName) {
...@@ -100,7 +105,7 @@ public class ExplorerController extends BaseController { ...@@ -100,7 +105,7 @@ public class ExplorerController extends BaseController {
return "/filter/pick"; return "/filter/pick";
} }
@RequestMapping(value = "/pick", method = RequestMethod.POST) @RequestMapping(value = "/explore/pick", method = RequestMethod.POST)
public String doPickFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter, public String doPickFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "pick", required = false) String[] pick, @RequestParam(value = "crop", required = false) String shortName) { @RequestParam(value = "pick", required = false) String[] pick, @RequestParam(value = "crop", required = false) String shortName) {
if (StringUtils.isNotBlank(shortName)) { if (StringUtils.isNotBlank(shortName)) {
...@@ -122,7 +127,7 @@ public class ExplorerController extends BaseController { ...@@ -122,7 +127,7 @@ public class ExplorerController extends BaseController {
return "redirect:/explore/pick"; return "redirect:/explore/pick";
} }
@RequestMapping(value = "/pick", method = RequestMethod.POST, params = { "doView" }) @RequestMapping(value = "/explore/pick", method = RequestMethod.POST, params = { "doView" })
public String doView(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter, public String doView(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "pick", required = false) String[] pick, @RequestParam(value = "crop", required = false) String shortName) { @RequestParam(value = "pick", required = false) String[] pick, @RequestParam(value = "crop", required = false) String shortName) {
...@@ -131,15 +136,18 @@ public class ExplorerController extends BaseController { ...@@ -131,15 +136,18 @@ public class ExplorerController extends BaseController {
return "redirect:/explore/filter"; return "redirect:/explore/filter";
} }
@RequestMapping(value = "/filter", method = RequestMethod.GET)
public String showFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter,
@RequestParam(value = "pick", required = false, defaultValue = "[]") String pick, @RequestParam(value = "crop", required = false) String shortName) {
String[] selectedFilters = new String[0]; @RequestMapping(value = "/explore/filter", method = RequestMethod.GET)
try { public String showFilters(ModelMap model, @RequestParam(value = "filter", required = false, defaultValue = "{}") String jsonFilter,
selectedFilters = mapper.readValue(pick, selectedFilters.getClass()); @RequestParam(value = "pick", required = false) String pick, @RequestParam(value = "crop", required = false) String shortName) {
} catch (IOException e) {
_logger.error(e.getMessage(), e); String[] selectedFilters = GenesysFilterService.DEFAULT_FILTERS;
if (StringUtils.isNotBlank(pick)) {
try {
selectedFilters = mapper.readValue(pick, selectedFilters.getClass());
} catch (NullPointerException | IOException e) {
_logger.error(e.getMessage(), e);
}
} }
model.addAttribute("selectedFilters", filterService.selectFilters(selectedFilters)); model.addAttribute("selectedFilters", filterService.selectFilters(selectedFilters));
...@@ -147,13 +155,12 @@ public class ExplorerController extends BaseController { ...@@ -147,13 +155,12 @@ public class ExplorerController extends BaseController {
try { try {
JsonNode jsonTree = mapper.readTree(jsonFilter); JsonNode jsonTree = mapper.readTree(jsonFilter);
model.addAttribute("jsonString", jsonTree.toString()); model.addAttribute("jsonString", jsonTree.toString());
} catch (IOException e) { } catch (NullPointerException | IOException e) {
_logger.warn(e.getMessage(), e); _logger.warn(e.getMessage(), e);
} }
try { try {
JsonNode jsonTree = mapper.readTree(pick); model.addAttribute("pick", mapper.writeValueAsString(selectedFilters));
model.addAttribute("pick", jsonTree.toString());
} catch (IOException e) { } catch (IOException e) {
_logger.warn(e.getMessage(), e); _logger.warn(e.getMessage(), e);
} }
...@@ -168,7 +175,47 @@ public class ExplorerController extends BaseController { ...@@ -168,7 +175,47 @@ public class ExplorerController extends BaseController {
return "/filter/filter"; return "/filter/filter";
} }
@RequestMapping(value = "/ac/{field}", produces = MediaType.APPLICATION_JSON_VALUE)
/**
* Browse all
*
* @param model
* @param page
* @return
*/
@RequestMapping("/explore")
public String viewFiltered(ModelMap model, @RequestParam(value = "page", required = false, defaultValue = "1") int page,
@RequestParam(value = "filter", required = true, defaultValue = "{}") String jsonFilter) {
_logger.info("Filtering by: " + jsonFilter);
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonTree = null;
try {
jsonTree = mapper.readTree(jsonFilter);
_logger.debug(jsonTree.toString());
model.addAttribute("jsonString", jsonTree.toString());
model.addAttribute("jsonFilter", jsonTree);
Iterator<Entry<String, JsonNode>> fields = jsonTree.fields();
while (fields.hasNext()) {
Entry<String, JsonNode> entry = fields.next();
_logger.debug("2=" + entry.getKey() + " = " + entry.getValue());
}
} catch (IOException e) {
e.printStackTrace();
}
Page<Accession> accessions = filterService.listAccessions(jsonTree, new PageRequest(page - 1, 50, new Sort("acceNumb")));
_logger.info("Got: " + accessions);
model.addAttribute("pagedData", accessions);
return "/accession/explore";
}
@RequestMapping(value = "/explore/ac/{field}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody @ResponseBody
public List<LabelValue<String>> autocomplete(@PathVariable("field") String filter, @RequestParam(value = "term", required = false) String ac) { public List<LabelValue<String>> autocomplete(@PathVariable("field") String filter, @RequestParam(value = "term", required = false) String ac) {
return filterService.autocomplete(filter, ac); return filterService.autocomplete(filter, ac);
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<div class="navbar-collapse"> <div class="navbar-collapse">
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="notimportant"><a href="<c:url value="/" />"><spring:message code="menu.home" /></a></li> <li class="notimportant"><a href="<c:url value="/" />"><spring:message code="menu.home" /></a></li>
<li><a href="<c:url value="/acn/" />"><spring:message code="menu.browse" /></a></li> <li><a href="<c:url value="/explore" />"><spring:message code="menu.browse" /></a></li>
<li><a href="<c:url value="/data/" />"><spring:message code="menu.datasets" /></a></li> <li><a href="<c:url value="/data/" />"><spring:message code="menu.datasets" /></a></li>
<%-- <li><a href="<c:url value="/descriptors/" />"><spring:message code="menu.descriptors" /></a></li> --%> <%-- <li><a href="<c:url value="/descriptors/" />"><spring:message code="menu.descriptors" /></a></li> --%>
<li><a href="<c:url value="/geo/" />"><spring:message code="menu.countries" /></a></li> <li><a href="<c:url value="/geo/" />"><spring:message code="menu.countries" /></a></li>
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<div class="pagination"> <div class="pagination">
<spring:message code="paged.pageOfPages" arguments="${pagedData.number+1},${pagedData.totalPages}" /> <spring:message code="paged.pageOfPages" arguments="${pagedData.number+1},${pagedData.totalPages}" />
<a href="<spring:url value=""><spring:param name="page" value="${pagedData.number eq 0 ? 1 : pagedData.number}" /><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="pagination.previous-page" /></a> <a href="<spring:url value=""><spring:param name="page" value="${pagedData.number eq 0 ? 1 : pagedData.number}" /><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="pagination.previous-page" /></a>
<a href="<spring:url value=""><spring:param name="page" value="${pagedData.number+2}" /><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="pagination.next-page" /></a> <a href="<spring:url value=""><spring:param name="page" value="${pagedData.number+2}" /><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="pagination.next-page" /></a>
</div> </div>
</div> </div>
</div> </div>
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
<c:if test="${filters eq null and jsonFilter ne null}"> <c:if test="${filters eq null and jsonFilter ne null}">
<div class="applied-filters"> <div class="applied-filters">
<spring:message code="filters.data-is-filtered" /> <spring:message code="filters.data-is-filtered" />
<a href="<spring:url value="/acn/filter"><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="filters.modify-filters" /></a> <a href="<spring:url value="/explore/filter"><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="filters.modify-filters" /></a>
</div> </div>
</c:if> </c:if>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
</div> </div>
<c:remove var="clazz" /> <c:remove var="clazz" />
</c:forEach> </c:forEach>
<a href="<spring:url value="/acn/filter"><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="filters.modify-filters" /></a> <a href="<spring:url value="/explore/filter"><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="filters.modify-filters" /></a>
</div> </div>
</c:if> </c:if>
......
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="accession.page.data.title" /></title>
</head>
<body>
<h1>
<spring:message code="accession.page.data.title" />
</h1>
<div class="main-col-header clearfix">
<div class="nav-header pull-left">
<div class="results"><spring:message code="accessions.number" arguments="${pagedData.totalElements}" /></div>
<div class="pagination">
<spring:message code="paged.pageOfPages" arguments="${pagedData.number+1},${pagedData.totalPages}" />
<a href="<spring:url value=""><spring:param name="page" value="${pagedData.number eq 0 ? 1 : pagedData.number}" /><spring:param name="filter" value="${jsonFilter}" /></spring:url>"><spring:message code="pagination.previous-page" /></a>
<a href="<spring:url value=""><spring:param name="page" value="${pagedData.number+2}" /><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="pagination.next-page" /></a>
</div>
</div>
</div>
<c:if test="${filters eq null and jsonFilter ne null}">
<div class="applied-filters">
<spring:message code="filters.data-is-filtered" />
<a href="<spring:url value="/explore/filter"><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="filters.modify-filters" /></a>
</div>
</c:if>
<c:if test="${filters ne null}">
<div class="applied-filters">
<c:forEach items="${filters.keySet()}" var="by">
<c:set value="${filters[by].getClass().simpleName}" var="clazz" />
<div>
<spring:message code="${by}" />: <b>
<c:choose>
<c:when test="${clazz eq 'Taxonomy'}">
${filters[by].taxonName}
</c:when>
<c:when test="${clazz eq 'Crop'}">
<a href="<c:url value="/c/${filters[by].shortName}" />">${filters[by].getName(pageContext.response.locale)}</a>
</c:when>
<c:when test="${clazz eq 'Country'}">
<a href="<c:url value="/geo/${filters[by].code3.toLowerCase()}" />"><c:out value="${filters[by].getName(pageContext.response.locale)}" /></a>
</c:when>
<c:when test="${clazz eq 'FaoInstitute'}">
<a href="<c:url value="/wiews/${filters[by].code.toLowerCase()}" />"><c:out value="${filters[by].fullName}" /></a>
</c:when>
<c:when test="${clazz eq 'Organization'}">
<a href="<c:url value="/org/${filters[by].slug}" />"><c:out value="${filters[by].title}" /></a>
</c:when>
<c:otherwise>
${filters[by]}
</c:otherwise>
</c:choose>
</b>
</div>
<c:remove var="clazz" />
</c:forEach>
<a href="<spring:url value="/explore/filter"><spring:param name="filter" value="${jsonFilter}" /><spring:param name="pick" value="${jsonPick}" /></spring:url>"><spring:message code="filters.modify-filters" /></a>
</div>
</c:if>
<table class="accessions">
<thead>
<tr>
<td class="idx-col"></td>
<td />
<td><spring:message code="accession.accessionName" /></td>
<td><spring:message code="accession.taxonomy" /></td>
<td class="notimportant"><spring:message code="accession.origin" /></td>
<td class="notimportant"><spring:message code="accession.sampleStatus" /></td>
<td class="notimportant"><spring:message code="accession.holdingInstitute" /></td>
<%-- <td><spring:message code="accession.holdingCountry" /></td>
--%>
</tr>
</thead>
<tbody>
<c:forEach items="${pagedData.content}" var="accession" varStatus="status">
<tr class="acn ${status.count % 2 == 0 ? 'even' : 'odd'}">
<td class="idx-col">${status.count + pagedData.size * pagedData.number}</td>
<td class="sel" x-aid="${accession.id}"></td>
<td><a href="<c:url value="/acn/id/${accession.id}" />"><b><c:out value="${accession.accessionName}" /></b></a></td>
<%-- <td><a href="<c:url value="/acn/t/${accession.taxonomy.genus}/${accession.taxonomy.species}" />"><c:out value="${accession.taxonomy.taxonName}" /></a></td> --%>
<td><c:out value="${accession.taxonomy.taxonName}" /></td>
<%-- <td class="notimportant"><a href="<c:url value="/geo/${accession.origin.toLowerCase()}" />"><c:out value="${accession.countryOfOrigin.name}" /></a></td> --%>
<td class="notimportant"><c:out value="${accession.countryOfOrigin.getName(pageContext.response.locale)}" /></td>
<td class="notimportant"><spring:message code="accession.sampleStatus.${accession.sampleStatus}" /></td>
<td class="notimportant"><a href="<c:url value="/wiews/${accession.institute.code.toLowerCase()}" />"><c:out value="${accession.institute.code}" /></a></td>
<%-- <td><a href="<c:url value="/geo/${accession.institute.country.code3.toLowerCase()}" />"><c:out value="${accession.institute.country.name}" /></a></td>
--%>
</tr>
</c:forEach>
</tbody>
</table>
</body>
</html>
\ No newline at end of file
...@@ -90,10 +90,10 @@ jQuery(document).ready(function() { ...@@ -90,10 +90,10 @@ jQuery(document).ready(function() {
var filters={}; var filters={};
try { try {
filters=$.parseJSON('${jsonString}'); filters=$.parseJSON('${jsonString}');
} catch (e) {filters={}; console.error(e);} } catch (e) {console.error(e);}
try { try {
pick=$.parseJSON('${pick}'); pick=$.parseJSON('${pick}');
} catch (e) {filters={}; console.error(e);} } catch (e) {console.error(e);}
// debugger; // debugger;
console.log("Filters: " +JSON.stringify(filters)); console.log("Filters: " +JSON.stringify(filters));
...@@ -104,6 +104,12 @@ jQuery(document).ready(function() { ...@@ -104,6 +104,12 @@ jQuery(document).ready(function() {
//alert(c.data("filtername")); //alert(c.data("filtername"));
c.empty(); c.empty();
if (values==null) return; if (values==null) return;
var checkboxes=c.prev().find("input[type=checkbox]")
if (checkboxes.length>0) {
values.forEach(function(i, idx) {
checkboxes.each(function(){if (this.value==''+i) this.checked=true;});
});
}
values.forEach(function(i, idx) { values.forEach(function(i, idx) {
console.log("Adding: " + i); console.log("Adding: " + i);
//debugger; //debugger;
...@@ -127,15 +133,15 @@ jQuery(document).ready(function() { ...@@ -127,15 +133,15 @@ jQuery(document).ready(function() {
refreshJson: function(newFilter) { refreshJson: function(newFilter) {
$("#filtersJson").html(JSON.stringify(newFilter)); $("#filtersJson").html(JSON.stringify(newFilter));
$("#filtersHref").attr("href", "/acn/f?<c:if test="${crop ne null}">crop=${crop.shortName}&</c:if>filter="+JSON.stringify(newFilter)+"&pick="+JSON.stringify(pick)); $("#filtersHref").attr("href", "<c:url value="/explore" />?<c:if test="${crop ne null}">crop=${crop.shortName}&</c:if>filter="+JSON.stringify(newFilter)+"&pick="+JSON.stringify(pick));
}, },
addFilterValue: function(element) { addFilterValue: function(element) {
var name=$(element).closest(".clearfix").attr("x-filtername").replace('_',':'); var name=$(element).closest(".clearfix").attr("x-filtername").replace('_',':');
//debugger; //debugger;
var a=null, b=null; var a=null, b=null;
var inputs=$(element).parent().children("input"); var inputs=$(element).parent().children("input[type=text]");
var selects=$(element).parent().children("select"); var checkboxes=$(element).parent().children("input[type=checkbox]");
var r=null; var r=null;
if (inputs.length==1) { if (inputs.length==1) {
// add 1 // add 1
...@@ -158,9 +164,12 @@ jQuery(document).ready(function() { ...@@ -158,9 +164,12 @@ jQuery(document).ready(function() {
r={"range":[parseFloat(a),parseFloat(b)]}; r={"range":[parseFloat(a),parseFloat(b)]};
if (isNaN(r["range"][0]) || isNaN(r["range"][1])) r=null; if (isNaN(r["range"][0]) || isNaN(r["range"][1])) r=null;
} }
} else if (selects.length==1) { } else if (checkboxes.length==1) {
a=selects[0].value; a=checkboxes[0].value;
r = (a == 'true' ? true : a=='null' ? 'null' : false); if (a=='true' || a=='false' || a=='null')
r = (a == 'true' ? true : a=='null' ? 'null' : false);
else
r=a;
} }
console.log(r); console.log(r);
...@@ -196,7 +205,7 @@ jQuery(document).ready(function() { ...@@ -196,7 +205,7 @@ jQuery(document).ready(function() {
} }
}; };
[<c:forEach items="${availableFilters}" var="filter" varStatus="status">${status.index gt 0 ? ',' : ''} "${filter.name.replace(':','_')}"</c:forEach> ].forEach(function(name) { [<c:forEach items="${selectedFilters}" var="filter" varStatus="status">${status.index gt 0 ? ',' : ''} "${filter.name.replace(':','_')}"</c:forEach> ].forEach(function(name) {
FF.updateValues("#filter-" + name + ".filter-values", name, filters[name]); FF.updateValues("#filter-" + name + ".filter-values", name, filters[name]);
}); });
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<c:forEach items="${availableFilters}" var="filter"> <c:forEach items="${availableFilters}" var="filter">
<div class="row filter-block"> <div class="row filter-block">
<div class="col-lg-offset-3 col-lg-9"> <div class="col-lg-offset-3 col-lg-9">
<div><label><input type="checkbox" name="pick" <c:if test="${selectedFilters.contains(filter.key)}">checked="checked"</c:if> value="${filter.key}" /><span dir="ltr"><spring:message code="filter.${filter.key}" /></span></label></div> <div><label><input type="checkbox" id="picker-${filter.key}" name="pick" <c:if test="${selectedFilters.contains(filter.key)}">checked="checked"</c:if> value="${filter.key}" /><span dir="ltr"><spring:message code="filter.${filter.key}" /></span></label></div>
</div> </div>
</div> </div>
</c:forEach> </c:forEach>
...@@ -74,6 +74,12 @@ ...@@ -74,6 +74,12 @@
jQuery(document).ready(function() { jQuery(document).ready(function() {
$("#cropselector").on("change", function(e) { $("#cropselector").on("change", function(e) {
e.preventDefault(); e.preventDefault();
if (this.value=='') {
$("#picker-crop")[0].disabled=false;
} else {
$("#picker-crop")[0].disabled=true;
$("#picker-crop")[0].checked=false;
}
this.form.submit(); this.form.submit();
}); });
}); });
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
</h1> </h1>
<div class="main-col-header"> <div class="main-col-header">
<a href="<c:url value="/acn/f"><c:param name="filter">{"mls":["Y"]}</c:param></c:url>"><spring:message code="view.accessions" /></a> <a href="<c:url value="/acn/f?filter={"mls":[true]}" />"><spring:message code="view.accessions" /></a>
</div> </div>
<%@include file="/WEB-INF/jsp/content/include/blurp-display.jsp"%> <%@include file="/WEB-INF/jsp/content/include/blurp-display.jsp"%>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment