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

knowledgepixels / nanodash / 28789064412

06 Jul 2026 11:44AM UTC coverage: 28.51% (+0.008%) from 28.502%
28789064412

push

github

web-flow
Merge pull request #535 from knowledgepixels/feat/navigation-context

feat: persistent navigation context with back-link and post-publish forwarding

1811 of 7177 branches covered (25.23%)

Branch coverage included in aggregate %.

3710 of 12188 relevant lines covered (30.44%)

4.52 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/MaintainedResourcePage.java
1
package com.knowledgepixels.nanodash.page;
2

3
import com.knowledgepixels.nanodash.NanodashPageRef;
4
import com.knowledgepixels.nanodash.SpaceMemberRole;
5
import com.knowledgepixels.nanodash.View;
6
import com.knowledgepixels.nanodash.ViewDisplay;
7
import com.knowledgepixels.nanodash.component.*;
8
import com.knowledgepixels.nanodash.domain.AbstractResourceWithProfile;
9
import com.knowledgepixels.nanodash.domain.MaintainedResource;
10
import com.knowledgepixels.nanodash.domain.Space;
11
import com.knowledgepixels.nanodash.repository.MaintainedResourceRepository;
12
import org.apache.wicket.Component;
13
import org.apache.wicket.ajax.AjaxRequestTarget;
14
import org.apache.wicket.extensions.ajax.markup.html.AjaxLazyLoadPanel;
15
import org.apache.wicket.markup.html.WebMarkupContainer;
16
import org.apache.wicket.markup.html.basic.Label;
17
import org.apache.wicket.markup.html.panel.EmptyPanel;
18
import org.apache.wicket.model.IModel;
19
import org.apache.wicket.model.LoadableDetachableModel;
20
import org.apache.wicket.model.Model;
21
import org.apache.wicket.request.mapper.parameter.PageParameters;
22
import org.eclipse.rdf4j.model.util.Values;
23

24
import java.util.List;
25
import java.util.Optional;
26

27
/**
28
 * This class represents a page for a maintained resource.
29
 */
30
public class MaintainedResourcePage extends NanodashPage {
31

32
    /**
33
     * The mount path for this page.
34
     */
35
    public static final String MOUNT_PATH = "/resource";
36

37
    /**
38
     * {@inheritDoc}
39
     */
40
    @Override
41
    public String getMountPath() {
42
        return MOUNT_PATH;
×
43
    }
44

45
    @Override
46
    public String getContextId() {
47
        return resourceId;
×
48
    }
49

50
    @Override
51
    public boolean isContextPage() {
52
        return true;
×
53
    }
54

55
    /**
56
     * Id of the maintained resource shown on this page. Only the id is held in
57
     * the page state; the {@link MaintainedResource} itself is re-fetched from
58
     * the repository on every render via {@link #resourceModel}, so the page
59
     * tree never carries a serialized snapshot of singleton data.
60
     */
61
    private final String resourceId;
62

63
    /**
64
     * LDM that resolves {@link #resourceId} to the live {@link MaintainedResource} singleton.
65
     */
66
    private final IModel<MaintainedResource> resourceModel;
67

68
    public MaintainedResourcePage(final PageParameters parameters) {
69
        super(parameters);
×
70

71
        MaintainedResource resource = MaintainedResourceRepository.get().findById(parameters.get("id").toString());
×
72
        resourceId = resource.getId();
×
73
        resourceModel = new LoadableDetachableModel<MaintainedResource>() {
×
74
            @Override
75
            protected MaintainedResource load() {
76
                return MaintainedResourceRepository.get().findById(resourceId);
×
77
            }
78
        };
79
        resource.triggerDataUpdate();
×
80

81
        ResourceTabs.Tab activeTab = ResourceTabs.activeFromParam(parameters);
×
82

83
        List<AbstractResourceWithProfile> superSpaces = resource.getAllSuperSpacesUntilRoot();
×
84
        superSpaces.add(resource.getSpace());
×
85
        superSpaces.add(resource);
×
86
        add(new TitleBar("titlebar", this, null,
×
87
                superSpaces.stream().map(ss -> new NanodashPageRef(SpacePage.class, new PageParameters().add("id", ss.getId()), ss.getLabel())).toArray(NanodashPageRef[]::new)
×
88
        ).setTabs(new ResourceTabs("tabs", "resource", resource.getId(), activeTab)));
×
89

90
        add(new Label("pagetitle", resource.getLabel() + " (resource) | nanodash"));
×
91
        add(new Label("resourcename", resource.getLabel()));
×
92
        add(new Label("titlesuffix", ResourceTabs.titleSuffix(activeTab)));
×
93
        add(new ExternalLinkWithActionsPanel("id", Model.of(resource.getId()), Model.of(resource.getLabel()), Values.iri(resource.getNanopubId())));
×
94

95
        WebMarkupContainer contentContainer = new WebMarkupContainer("contentContainer");
×
96
        add(contentContainer);
×
97
        if (activeTab == ResourceTabs.Tab.CONTENT) {
×
98
            add(new EmptyPanel("otherTab").setVisible(false));
×
99
            if (resource.isDataInitialized()) {
×
100
                boolean empty = resource.getTopLevelViewDisplays().isEmpty();
×
101
                if (empty) {
×
102
                    contentContainer.add(new WebMarkupContainer("views").setVisible(false));
×
103
                } else {
104
                    contentContainer.add(new ViewList("views", resource));
×
105
                }
106
                addUnconfiguredFallback(contentContainer, resource, empty);
×
107
            } else {
×
108
                // Data not yet loaded: render the views lazily, then reveal the unconfigured
109
                // notice + general-info fallback once we know whether any views exist.
110
                final WebMarkupContainer unconfiguredNotice = new WebMarkupContainer("unconfigured-notice");
×
111
                unconfiguredNotice.setVisible(false);
×
112
                unconfiguredNotice.setOutputMarkupPlaceholderTag(true);
×
113
                contentContainer.add(unconfiguredNotice);
×
114

115
                final ViewList generalInfoView = new ViewList("generalinfoview", resource, List.of(generalInfoViewDisplay()));
×
116
                generalInfoView.setVisible(false);
×
117
                generalInfoView.setOutputMarkupPlaceholderTag(true);
×
118
                contentContainer.add(generalInfoView);
×
119

120
                contentContainer.add(new AjaxLazyLoadPanel<Component>("views") {
×
121

122
                    @Override
123
                    public Component getLazyLoadComponent(String markupId) {
124
                        return new ViewList(markupId, resourceModel.getObject());
×
125
                    }
126

127
                    @Override
128
                    protected boolean isContentReady() {
129
                        return resourceModel.getObject().isDataInitialized();
×
130
                    }
131

132
                    @Override
133
                    public Component getLoadingComponent(String id) {
134
                        return new Label(id, "<div class=\"row-section\"><div class=\"col-12\">" + ResultComponent.getWaitIconHtml() + "</div></div>").setEscapeModelStrings(false);
×
135
                    }
136

137
                    @Override
138
                    protected void onContentLoaded(Component content, Optional<AjaxRequestTarget> target) {
139
                        super.onContentLoaded(content, target);
×
140
                        target.ifPresent(t -> {
×
141
                            boolean isEmpty = resourceModel.getObject().getTopLevelViewDisplays().isEmpty();
×
142
                            if (isEmpty) {
×
143
                                t.appendJavaScript("document.getElementById('" + getMarkupId() + "').remove();");
×
144
                            }
145
                            unconfiguredNotice.setVisible(isEmpty);
×
146
                            t.add(unconfiguredNotice);
×
147
                            generalInfoView.setVisible(isEmpty);
×
148
                            t.add(generalInfoView);
×
149
                        });
×
150
                    }
×
151

152
                });
153
            }
×
154
        } else {
155
            contentContainer.setVisible(false);
×
156
            if (activeTab == ResourceTabs.Tab.ABOUT) {
×
157
                // The panel constructor resolves view nanopubs over the network when
158
                // they aren't freshly cached, which would block the initial page
159
                // render; the view-id list must mirror the panel's View.get calls.
160
                add(LazyContentPanel.of("otherTab", markupId -> new AboutResourcePanel(markupId, resourceModel.getObject()),
×
161
                        AboutResourcePanel.MAINTAINED_RESOURCE_INFO_VIEW, AboutSpacePanel.PRESET_ASSIGNMENTS_VIEW, AboutSpacePanel.VIEW_DISPLAYS_VIEW));
162
            } else if (activeTab == ResourceTabs.Tab.EXPLORE) {
×
163
                add(LazyContentPanel.of("otherTab", markupId -> new ExplorePanel(markupId, resourceId),
×
164
                        ReferencesPage.REFERENCES_VIEW));
165
            } else {
166
                add(new DownloadRdfLinks("otherTab", "resource", resource.getId()));
×
167
            }
168
        }
169
    }
×
170

171
    /**
172
     * View shown as a general-information fallback on pages that have no view displays
173
     * configured yet.
174
     */
175
    private static final String GENERAL_INFO_VIEW = "https://w3id.org/np/RA9FKwtTWqknnDrFz1vMNeUdWpYYVQp7sznigpaXlBrU8/resource-info-view";
176

177
    private static ViewDisplay generalInfoViewDisplay() {
178
        return new ViewDisplay(View.get(GENERAL_INFO_VIEW));
×
179
    }
180

181
    /**
182
     * Adds the "page not configured yet" notice and the general-information fallback view,
183
     * both visible only when the resource has no view displays.
184
     */
185
    private void addUnconfiguredFallback(WebMarkupContainer contentContainer, AbstractResourceWithProfile resource, boolean empty) {
186
        contentContainer.add(new WebMarkupContainer("unconfigured-notice").setVisible(empty));
×
187
        if (empty) {
×
188
            contentContainer.add(new ViewList("generalinfoview", resource, List.of(generalInfoViewDisplay())));
×
189
        } else {
190
            contentContainer.add(new EmptyPanel("generalinfoview").setVisible(false));
×
191
        }
192
    }
×
193

194
    /**
195
     * Checks if auto-refresh is enabled for this page.
196
     *
197
     * @return true if auto-refresh is enabled, false otherwise
198
     */
199
    protected boolean hasAutoRefreshEnabled() {
200
        return true;
×
201
    }
202

203
    /**
204
     * {@inheritDoc}
205
     */
206
    @Override
207
    protected void onDetach() {
208
        resourceModel.detach();
×
209
        super.onDetach();
×
210
    }
×
211

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

© 2026 Coveralls, Inc