Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docker-compose.override.yml.template
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ services:
- NANODASH_MAIN_QUERY=https://query.knowledgepixels.com/
- NANODASH_UMAMI_SCRIPT_URL=https://your.umami.instance/script.js
- NANODASH_UMAMI_WEBSITE_ID=your-umami-website-id
# The maintained resource whose view displays are shown on the home page.
# Default: https://w3id.org/spaces/knowledgepixels/nanodash/r/home
#- NANODASH_HOME_RESOURCE=https://w3id.org/spaces/your/resource/r/home

backup-keys:
build: ./backup-keys
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState;
import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
import org.apache.wicket.model.IModel;
import org.nanopub.extra.services.ApiResponse;
import org.nanopub.extra.services.ApiResponseEntry;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

Expand Down Expand Up @@ -65,7 +67,24 @@ public List<ApiResponseEntry> getFilteredData() {

@Override
public Iterator<? extends ApiResponseEntry> iterator(long first, long count) {
List<ApiResponseEntry> data = getFilteredData();
List<ApiResponseEntry> data = new ArrayList<>(getFilteredData());
SortParam<String> sortParam = baseProvider.getSortParam();
if (sortParam != null) {
String prop = sortParam.getProperty();
String labelProp = prop + "_label";
String sortProp = Arrays.asList(response.getHeader()).contains(labelProp) ? labelProp : prop;
data.sort((o1, o2) -> {
String v1 = o1.get(sortProp);
String v2 = o2.get(sortProp);
int result;
if (v1 == null && v2 == null) result = 0;
else if (v1 == null) result = 1;
else if (v2 == null) result = -1;
else result = v1.compareToIgnoreCase(v2);
if (!sortParam.isAscending()) result = -result;
return result;
});
}
return Utils.subList(data, first, first + count).iterator();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public static NanodashPreferences get() {
private String settingUri;
private String umamiScriptUrl;
private String umamiWebsiteId;
private String homeResource = "https://w3id.org/spaces/knowledgepixels/nanodash/r/home";
public static final String DEFAULT_SETTING_PATH = "/.nanopub/nanodash-preferences.yml";

/**
Expand Down Expand Up @@ -252,4 +253,18 @@ public void setUmamiWebsiteId(String umamiWebsiteId) {
this.umamiWebsiteId = umamiWebsiteId;
}

public String getHomeResource() {
String s = System.getenv("NANODASH_HOME_RESOURCE");
if (s != null && !s.isBlank()) {
logger.debug("Found environment variable NANODASH_HOME_RESOURCE with value: {}", s);
return s;
}
logger.debug("Environment variable NANODASH_HOME_RESOURCE not set, using default: {}", homeResource);
return homeResource;
}

public void setHomeResource(String homeResource) {
this.homeResource = homeResource;
}

}
3 changes: 0 additions & 3 deletions src/main/java/com/knowledgepixels/nanodash/QueryResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
import com.knowledgepixels.nanodash.domain.IndividualAgent;
import com.knowledgepixels.nanodash.page.NanodashPage;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.AbstractLink;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
Expand Down Expand Up @@ -49,8 +48,6 @@ public QueryResult(String markupId, QueryRef queryRef, ApiResponse response, Vie
this.viewDisplay = viewDisplay;
this.response = response;
this.grlcQuery = GrlcQuery.get(queryRef);

add(new AttributeAppender("class", " col-" + viewDisplay.getDisplayWidth()));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.apache.wicket.extensions.markup.html.repeater.data.sort.ISortState;
import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
import org.apache.wicket.extensions.markup.html.repeater.util.SingleSortState;
import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.nanopub.extra.services.ApiResponseEntry;
Expand Down Expand Up @@ -55,6 +56,10 @@ public ISortState<String> getSortState() {
return sortState;
}

public SortParam<String> getSortParam() {
return sortState.getSort();
}

@Override
public void detach() {
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/knowledgepixels/nanodash/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ public static String urlEncode(Object o) {
return URLEncoder.encode((o == null ? "" : o.toString()), Charsets.UTF_8);
}

public static String truncateLabel(String label) {
if (label != null && label.length() > 120) {
return label.substring(0, 100) + "...";
}
return label;
}

/**
* URL-decodes the string representation of the given object using UTF-8 encoding.
*
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/knowledgepixels/nanodash/ViewDisplay.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ public Integer getDisplayWidth() {
return 12;
}

public ViewDisplay withDisplayWidth(int width) {
this.displayWidth = width;
return this;
}

public String getStructuralPosition() {
if (structuralPosition != null) return structuralPosition;
if (view == null) return "5.5.default";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private void initialize() {
add(new Label("header", "").setVisible(false));
} else {
WebMarkupContainer header = new WebMarkupContainer("header");
String labelString = n.getLabel();
String labelString = Utils.truncateLabel(n.getLabel());
if (labelString == null || labelString.isBlank()) labelString = Utils.getShortNanopubId(n.getUri());
header.add(NanodashLink.createLink("nanopub-id-link", n.getUri(), labelString, null));
if (!hideActionMenu && (actions == null || !actions.isEmpty())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">

<head>
<link rel="stylesheet" href="../../../../../webapp/style.css?for-local-testing-only" type="text/css" media="screen"
title="Stylesheet"/>
</head>
<body style="margin-left: auto; margin-right: auto; max-width: 1420px">

<wicket:panel>
<div class="paneltitlerow listpanel">
<h4 wicket:id="label">Query</h4>
<input type="text" wicket:id="filter" placeholder="Filter..." style="margin: 5px 10px; padding: 2px 5px; font-size: 12px; width: 150px;"/>
<span class="buttons"><wicket:container wicket:id="np">[np]</wicket:container></span>
<span wicket:id="buttons" class="buttons"></span>
</div>

<div wicket:id="items-container">
<ul wicket:id="items">
<li wicket:id="listItem"></li>
</ul>
<div class="navigation" wicket:id="navigation">
<div class="navigatorLabel" wicket:id="navigatorLabel"></div>
<div class="navigator" wicket:id="navigator"></div>
</div>
</div>
</wicket:panel>

</body>

</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.knowledgepixels.nanodash.component;

import com.knowledgepixels.nanodash.*;
import com.knowledgepixels.nanodash.domain.IndividualAgent;
import com.knowledgepixels.nanodash.domain.User;
import com.knowledgepixels.nanodash.page.PublishPage;
import com.knowledgepixels.nanodash.page.UserPage;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.NavigatorLabel;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.ContextRelativeResourceReference;
import org.apache.wicket.util.string.Strings;
import org.eclipse.rdf4j.model.IRI;
import org.nanopub.extra.services.ApiResponse;
import org.nanopub.extra.services.ApiResponseEntry;
import org.nanopub.extra.services.QueryRef;

/**
* Component for displaying query results as a vertical item list.
* Each result row is rendered as a single linked item, with icon handling
* for user_iri and template_iri columns.
*/
public class QueryResultItemList extends QueryResult {

private FilteredQueryResultDataProvider filteredDataProvider;
private final Model<String> filterModel = Model.of("");
private WebMarkupContainer itemsContainer;

QueryResultItemList(String markupId, QueryRef queryRef, ApiResponse response, ViewDisplay viewDisplay) {
super(markupId, queryRef, response, viewDisplay);

String label = grlcQuery.getLabel();
if (viewDisplay.getTitle() != null) {
label = viewDisplay.getTitle();
}
add(new Label("label", label));
setOutputMarkupId(true);

TextField<String> filterField = new TextField<>("filter", filterModel);
filterField.setOutputMarkupId(true);
filterField.add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
if (filteredDataProvider != null && itemsContainer != null) {
filteredDataProvider.setFilterText(filterModel.getObject());
target.add(itemsContainer);
}
}
});
add(filterField);

populateComponent();
}

@Override
protected void populateComponent() {
QueryResultDataProvider dataProvider = new QueryResultDataProvider(response.getData());
filteredDataProvider = new FilteredQueryResultDataProvider(dataProvider, response);

DataView<ApiResponseEntry> dataView = new DataView<>("items", filteredDataProvider) {
@Override
protected void populateItem(Item<ApiResponseEntry> item) {
ApiResponseEntry entry = item.getModelObject();
for (String key : response.getHeader()) {
if (key.endsWith("_label") || key.endsWith("_label_multi")) continue;
String value = entry.get(key);
if (value == null || value.isBlank()) continue;
String entryLabel = entry.get(key + "_label");

if (key.endsWith("user_iri")) {
IRI userIri = Utils.vf.createIRI(value);
IRI profilePicIri = User.getProfilePicture(userIri);
String imgSrc;
String iconClass;
if (profilePicIri != null) {
imgSrc = Strings.escapeMarkup(profilePicIri.stringValue()).toString();
iconClass = "user-icon";
} else if (IndividualAgent.isSoftware(userIri)) {
imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/bot-icon.svg", false), null).toString();
iconClass = "bot-icon";
} else {
imgSrc = RequestCycle.get().urlFor(new ContextRelativeResourceReference("images/user-icon.svg", false), null).toString();
iconClass = "user-icon";
}
String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : User.getShortDisplayName(userIri);
String userUrl = UserPage.MOUNT_PATH + "?id=" + Utils.urlEncode(value);
String html = "<img class=\"" + iconClass + "\" src=\"" + imgSrc + "\" /> <a href=\"" + Strings.escapeMarkup(userUrl) + "\">" + Strings.escapeMarkup(displayLabel) + "</a>";
item.add(new Label("listItem", html).setEscapeModelStrings(false));
return;
} else if (key.endsWith("template_iri")) {
String displayLabel = (entryLabel != null && !entryLabel.isBlank()) ? entryLabel : value;
String templateUrl = PublishPage.MOUNT_PATH + "?template=" + Utils.urlEncode(value) + "&template-version=latest";
String html = "<span class=\"form-icon\"></span> <a href=\"" + Strings.escapeMarkup(templateUrl) + "\">" + Strings.escapeMarkup(displayLabel) + "</a>";
item.add(new Label("listItem", html).setEscapeModelStrings(false));
return;
} else if (value.matches("https?://.*")) {
item.add(new NanodashLink("listItem", value, null, null, entryLabel, contextId));
return;
} else {
item.add(new Label("listItem", value));
return;
}
}
item.add(new Label("listItem", ""));
}
};
dataView.setItemsPerPage(viewDisplay.getPageSize());

WebMarkupContainer navigation = new WebMarkupContainer("navigation");
navigation.add(new NavigatorLabel("navigatorLabel", dataView));
AjaxPagingNavigator pagingNavigator = new AjaxPagingNavigator("navigator", dataView);
navigation.setVisible(dataView.getPageCount() > 1);
navigation.add(pagingNavigator);

itemsContainer = new WebMarkupContainer("items-container");
itemsContainer.setOutputMarkupId(true);
itemsContainer.add(dataView);
itemsContainer.add(navigation);
add(itemsContainer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.knowledgepixels.nanodash.component;

import com.knowledgepixels.nanodash.ApiCache;
import com.knowledgepixels.nanodash.ViewDisplay;
import org.apache.wicket.behavior.AttributeAppender;
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
import org.apache.wicket.Component;
import org.nanopub.extra.services.ApiResponse;
import org.nanopub.extra.services.QueryRef;

import java.io.Serializable;

/**
* Builder class for creating QueryResultItemList components.
*/
public class QueryResultItemListBuilder implements Serializable {

private final String markupId;
private final ViewDisplay viewDisplay;
private String contextId = null;
private final QueryRef queryRef;
private AbstractResourceWithProfile pageResource = null;

private QueryResultItemListBuilder(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) {
this.markupId = markupId;
this.queryRef = queryRef;
this.viewDisplay = viewDisplay;
}

public static QueryResultItemListBuilder create(String markupId, QueryRef queryRef, ViewDisplay viewDisplay) {
return new QueryResultItemListBuilder(markupId, queryRef, viewDisplay);
}

public QueryResultItemListBuilder contextId(String contextId) {
this.contextId = contextId;
return this;
}

public QueryResultItemListBuilder resourceWithProfile(AbstractResourceWithProfile resourceWithProfile) {
return this;
}

public QueryResultItemListBuilder id(String id) {
return this;
}

public QueryResultItemListBuilder pageResource(AbstractResourceWithProfile pageResource) {
this.pageResource = pageResource;
return this;
}

public Component build() {
ApiResponse response = ApiCache.retrieveResponseAsync(queryRef);
String colClass = " col-" + viewDisplay.getDisplayWidth();
if (response != null) {
QueryResultItemList result = new QueryResultItemList(markupId, queryRef, response, viewDisplay);
result.setContextId(contextId);
result.setPageResource(pageResource);
result.add(new AttributeAppender("class", colClass));
return result;
} else {
ApiResultComponent comp = new ApiResultComponent(markupId, queryRef) {
@Override
public Component getApiResultComponent(String id, ApiResponse r) {
QueryResultItemList result = new QueryResultItemList(id, queryRef, r, viewDisplay);
result.setContextId(contextId);
result.setPageResource(pageResource);
return result;
}
};
comp.add(new AttributeAppender("class", colClass));
return comp;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ protected void populateItem(Item<ApiResponseEntry> item) {
item.add(listItem);
}
};
dataView.setItemsPerPage(10);
dataView.setItemsPerPage(viewDisplay.getPageSize());

WebMarkupContainer navigation = new WebMarkupContainer("navigation");
navigation.add(new NavigatorLabel("navigatorLabel", dataView));
Expand Down
Loading