• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanodash / 27129754740

08 Jun 2026 09:53AM UTC coverage: 20.681% (-0.2%) from 20.924%
27129754740

Pull #479

github

web-flow
Merge ecda3df99 into 5556185a4
Pull Request #479: About pages for spaces, maintained resources, and users (#478)

1051 of 6421 branches covered (16.37%)

Branch coverage included in aggregate %.

2640 of 11426 relevant lines covered (23.11%)

3.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/nanodash/page/SpacePage.java
1
package com.knowledgepixels.nanodash.page;
2

3
import com.knowledgepixels.nanodash.NanodashPageRef;
4
import com.knowledgepixels.nanodash.Utils;
5
import com.knowledgepixels.nanodash.component.*;
6
import com.knowledgepixels.nanodash.component.menu.SpaceExploreMenu;
7
import com.knowledgepixels.nanodash.connector.ConnectorConfig;
8
import com.knowledgepixels.nanodash.connector.GenOverviewPage;
9
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
10
import com.knowledgepixels.nanodash.SpaceMemberRole;
11
import com.knowledgepixels.nanodash.domain.MaintainedResource;
12
import com.knowledgepixels.nanodash.domain.Space;
13
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
14
import com.knowledgepixels.nanodash.repository.SpaceRepository;
15
import com.knowledgepixels.nanodash.vocabulary.KPXL_TERMS;
16
import org.apache.wicket.Component;
17
import org.apache.wicket.RestartResponseException;
18
import org.apache.wicket.extensions.ajax.markup.html.AjaxLazyLoadPanel;
19
import org.apache.wicket.markup.html.WebMarkupContainer;
20
import org.apache.wicket.markup.html.basic.Label;
21
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
22
import org.apache.wicket.markup.html.panel.EmptyPanel;
23
import org.apache.wicket.model.IModel;
24
import org.apache.wicket.model.LoadableDetachableModel;
25
import org.apache.wicket.model.Model;
26
import org.apache.wicket.request.mapper.parameter.PageParameters;
27
import org.nanopub.Nanopub;
28

29
import java.time.ZoneId;
30
import java.time.ZonedDateTime;
31
import java.time.format.DateTimeFormatter;
32
import java.util.List;
33

34
/**
35
 * The SpacePage class represents a space page in the Nanodash application.
36
 */
37
public class SpacePage extends NanodashPage {
38

39
    /**
40
     * The mount path for this page.
41
     */
42
    public static final String MOUNT_PATH = "/space";
43

44
    /**
45
     * {@inheritDoc}
46
     */
47
    @Override
48
    public String getMountPath() {
49
        return MOUNT_PATH;
×
50
    }
51

52
    /**
53
     * Id of the space shown on this page. Only the id is held in the page
54
     * state; the {@link Space} itself is re-fetched from the repository on
55
     * every render via {@link #spaceModel}, so the page tree never carries
56
     * a serialized snapshot of singleton data.
57
     */
58
    private final String spaceId;
59

60
    /**
61
     * LDM that resolves {@link #spaceId} to the live {@link Space} singleton.
62
     */
63
    private final IModel<Space> spaceModel;
64

65
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
×
66
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
×
67

68
    /**
69
     * Constructor for the SpacePage.
70
     *
71
     * @param parameters the page parameters
72
     */
73
    public SpacePage(final PageParameters parameters) {
74
        super(parameters);
×
75

76
        Space space = resolveSpace(parameters);
×
77
        spaceId = space.getId();
×
78
        spaceModel = new LoadableDetachableModel<Space>() {
×
79
            @Override
80
            protected Space load() {
81
                return SpaceRepository.get().findById(spaceId);
×
82
            }
83
        };
84
        space.triggerDataUpdate();
×
85

86
        Nanopub np = space.getNanopub();
×
87

88
        ResourceTabs.Tab activeTab = ResourceTabs.activeFromParam(parameters);
×
89

90
        List<AbstractResourceWithProfile> superSpaces = space.getAllSuperSpacesUntilRoot();
×
91
        if (superSpaces.isEmpty()) {
×
92
            add(new TitleBar("titlebar", this, null,
×
93
                    new NanodashPageRef(SpacePage.class, new PageParameters().add("id", space.getId()), space.getLabel())
×
94
            ).setTabs(new ResourceTabs("tabs", "space", space.getId(), activeTab)));
×
95
        } else {
96
            superSpaces.add(space);
×
97
            add(new TitleBar("titlebar", this, null,
×
98
                    superSpaces.stream().map(ss -> new NanodashPageRef(SpacePage.class, new PageParameters().add("id", ss.getId()), ss.getLabel())).toArray(NanodashPageRef[]::new)
×
99
            ).setTabs(new ResourceTabs("tabs", "space", space.getId(), activeTab)));
×
100
        }
101

102
        add(new JustPublishedMessagePanel("justPublishedMessage", parameters));
×
103

104
        add(new Label("pagetitle", space.getLabel() + " (space) | nanodash"));
×
105
        add(new Label("spacename", space.getLabel()));
×
106
        add(new Label("titlesuffix", ResourceTabs.titleSuffix(activeTab)));
×
107
        add(new Label("spacetype", space.getTypeLabel()));
×
108
        add(new ExternalLinkWithActionsPanel("id", Model.of(space.getId()), Model.of(space.getLabel()),
×
109
                new SpaceExploreMenu("np", space.getId(), space.getLabel(), np.getUri(), space)));
×
110

111
        add(new ItemListPanel<String>(
×
112
                "altids",
113
                "Alternative IDs:",
114
                space.getAltIDs(),
×
115
                i -> new ExternalLinkWithActionsPanel("item", Model.of(i), Model.of(i))
×
116
        ));
117

118
        WebMarkupContainer contentContainer = new WebMarkupContainer("contentContainer");
×
119
        add(contentContainer);
×
120
        if (activeTab != ResourceTabs.Tab.CONTENT) {
×
121
            contentContainer.setVisible(false);
×
122
            if (activeTab == ResourceTabs.Tab.ABOUT) {
×
123
                add(new AboutSpacePanel("otherTab", space));
×
124
            } else if (activeTab == ResourceTabs.Tab.EXPLORE) {
×
125
                add(new ExplorePanel("otherTab", space.getId()));
×
126
            } else {
127
                add(new DownloadRdfLinks("otherTab", "space", space.getId()));
×
128
            }
129
            return;
×
130
        }
131
        add(new EmptyPanel("otherTab").setVisible(false));
×
132

133
        if (space.getStartDate() != null) {
×
134
            ZoneId startZone = space.getStartDate().getTimeZone().toZoneId();
×
135
            ZonedDateTime startDt = ZonedDateTime.ofInstant(space.getStartDate().toInstant(), startZone);
×
136
            String dateString = DATE_FORMATTER.format(startDt);
×
137
            if (space.getEndDate() != null) {
×
138
                ZoneId endZone = space.getEndDate().getTimeZone().toZoneId();
×
139
                ZonedDateTime endDt = ZonedDateTime.ofInstant(space.getEndDate().toInstant(), endZone);
×
140
                String endDateStr = DATE_FORMATTER.format(endDt);
×
141
                if (dateString.equals(endDateStr)) {
×
142
                    String tzAbbr = startDt.getZone().getDisplayName(java.time.format.TextStyle.SHORT, java.util.Locale.ENGLISH);
×
143
                    dateString += " " + TIME_FORMATTER.format(startDt) + " - " + TIME_FORMATTER.format(endDt) + " " + tzAbbr;
×
144
                } else {
×
145
                    dateString += " - " + endDateStr;
×
146
                }
147
            }
148
            contentContainer.add(new Label("date", dateString));
×
149
        } else {
×
150
            contentContainer.add(new Label("date").setVisible(false));
×
151
        }
152

153
        contentContainer.add(new Label("description", "<span>" + Utils.sanitizeHtml(space.getDescription()) + "</span>").setEscapeModelStrings(false));
×
154

155
        if (space.isDataInitialized()) {
×
156
            contentContainer.add(new ViewList("views", space));
×
157
        } else {
158
            contentContainer.add(new AjaxLazyLoadPanel<Component>("views") {
×
159

160
                @Override
161
                public Component getLazyLoadComponent(String markupId) {
162
                    return new ViewList(markupId, spaceModel.getObject());
×
163
                }
164

165
                @Override
166
                protected boolean isContentReady() {
167
                    return spaceModel.getObject().isDataInitialized();
×
168
                }
169

170
                @Override
171
                public Component getLoadingComponent(String id) {
172
                    return new Label(id, "<div class=\"row-section\"><div class=\"col-12\">" + ResultComponent.getWaitIconHtml() + "</div></div>").setEscapeModelStrings(false);
×
173
                }
174

175
            });
176
        }
177

178
        contentContainer.add(new ItemListPanel<>(
×
179
                        "roles",
180
                        "Roles:",
181
                        () -> spaceModel.getObject().isDataInitialized(),
×
182
                        () -> spaceModel.getObject().getRoles(),
×
183
                        r -> new ItemListElement("item", ExplorePage.class, new PageParameters().set("id", r.getRole().getId()), r.getRole().getName(), null, Utils.getAsNanopub(r.getNanopubUri()))
×
184
                )
185
                        .makeInline()
×
186
                        .setResourceWithProfile(space)
×
187
                        .addAdminButton("+", PublishPage.class, new PageParameters()
×
188
                                .set("template", "https://w3id.org/np/RARBzGkEqiQzeiHk0EXFcv9Ol1d-17iOh9MoFJzgfVQDc")
×
189
                                .set("param_space", space.getId())
×
190
                                .set("refresh-upon-publish", space.getId())
×
191
                                .set("template-version", "latest")
×
192
                        )
193
        );
194

195
        if (space.isDataInitialized()) {
×
196
            contentContainer.add(new SpaceUserList("user-lists", space));
×
197
        } else {
198
            contentContainer.add(new AjaxLazyLoadPanel<Component>("user-lists") {
×
199

200
                @Override
201
                public Component getLazyLoadComponent(String markupId) {
202
                    return new SpaceUserList(markupId, spaceModel.getObject());
×
203
                }
204

205
                @Override
206
                protected boolean isContentReady() {
207
                    return spaceModel.getObject().isDataInitialized();
×
208
                }
209

210
            });
211
        }
212

213
        addSubspacePanel(contentContainer, "Alliance");
×
214
        addSubspacePanel(contentContainer, "Consortium");
×
215
        addSubspacePanel(contentContainer, "Organization");
×
216
        addSubspacePanel(contentContainer, "Taskforce");
×
217
        addSubspacePanel(contentContainer, "Division");
×
218
        addSubspacePanel(contentContainer, "Taskunit");
×
219
        addSubspacePanel(contentContainer, "Group");
×
220
        addSubspacePanel(contentContainer, "Project");
×
221
        addSubspacePanel(contentContainer, "Program");
×
222
        addSubspacePanel(contentContainer, "Initiative");
×
223
        addSubspacePanel(contentContainer, "Outlet");
×
224
        addSubspacePanel(contentContainer, "Campaign");
×
225
        addSubspacePanel(contentContainer, "Community");
×
226
        addSubspacePanel(contentContainer, "Event");
×
227

228
        contentContainer.add(new ItemListPanel<MaintainedResource>(
×
229
                "resources",
230
                "📦 Maintained Resources",
231
                () -> true,
×
232
                () -> MaintainedResourceRepository.get().findResourcesBySpace(spaceModel.getObject()),
×
233
                (resource) -> new ItemListElement("item", MaintainedResourcePage.class, new PageParameters().set("id", resource.getId()), resource.getLabel())
×
234
        ));
235

236
        String shortId = space.getId().replace("https://w3id.org/spaces/", "");
×
237
        ConnectorConfig cc = ConnectorConfig.get(shortId);
×
238
        if (cc != null) {
×
239
            contentContainer.add(new BookmarkablePageLink<Void>("content-button", GenOverviewPage.class, new PageParameters().set("journal", shortId)).setBody(Model.of("Nanopublication Submissions")));
×
240
        } else {
241
            contentContainer.add(new Label("content-button").setVisible(false));
×
242
        }
243
    }
×
244

245
    private void addSubspacePanel(WebMarkupContainer container, String type) {
246
        String typePl = type + "s";
×
247
        typePl = typePl.replaceFirst("ys$", "ies");
×
248

249
        container.add(new ItemListPanel<>(
×
250
                        typePl.toLowerCase(),
×
251
                        Space.getTypeEmoji(type) + " " + typePl,
×
252
                        SpaceRepository.get().findSubspaces(spaceModel.getObject(), KPXL_TERMS.NAMESPACE + type),
×
253
                        (subspace) -> new ItemListElement("item", SpacePage.class, new PageParameters().set("id", subspace), subspace.getLabel())
×
254
                )
255
        );
256
    }
×
257

258
    /**
259
     * Checks if auto-refresh is enabled for this page.
260
     *
261
     * @return true if auto-refresh is enabled, false otherwise
262
     */
263
    protected boolean hasAutoRefreshEnabled() {
264
        return true;
×
265
    }
266

267
    /**
268
     * {@inheritDoc}
269
     */
270
    @Override
271
    protected void onDetach() {
272
        spaceModel.detach();
×
273
        super.onDetach();
×
274
    }
×
275

276
    /**
277
     * Resolves the {@link Space} from the repository, or redirects as needed.
278
     *
279
     * @param parameters page parameters containing the space {@code id}
280
     * @return the resolved {@link Space}; never {@code null}
281
     * @throws RestartResponseException if the id belongs to a {@link MaintainedResource} or to a part within one
282
     * @throws IllegalArgumentException if the id cannot be resolved to any known resource
283
     */
284
    private Space resolveSpace(PageParameters parameters) {
285
        String id = parameters.get("id").toString();
×
286
        Space resolved = SpaceRepository.get().findById(id);
×
287
        if (resolved == null) {
×
288
            if (MaintainedResourceRepository.get().findById(id) != null) {
×
289
                throw new RestartResponseException(MaintainedResourcePage.class, parameters);
×
290
            }
291
            MaintainedResource containingResource = MaintainedResourceRepository.get().findByNamespace(MaintainedResource.getNamespace(id));
×
292
            if (containingResource != null) {
×
293
                PageParameters partParameters = new PageParameters(parameters);
×
294
                partParameters.set("context", containingResource.getId());
×
295
                throw new RestartResponseException(ResourcePartPage.class, partParameters);
×
296
            }
297
            throw new IllegalArgumentException("No space or resource found for id: " + id);
×
298
        }
299

300
        return resolved;
×
301
    }
302

303
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc