Improve subscriptions empty state rendering

This commit is contained in:
thePR0M3TH3AN committed 2025-10-20 21:39:08 -04:00
1 parent c1d8a8b9dc
commit 2322b19a3f
2 files changed
+136 -8

No files matched your search

+78 -8
View File
@@ -339,7 +339,14 @@ class SubscriptionsManager {
}
if (!this.loaded) {
await this.loadSubscriptions(userPubkey);
try {
await this.loadSubscriptions(userPubkey);
} catch (error) {
userLogger.error(
"[SubscriptionsManager] Failed to load subscriptions while rendering feed:",
error,
);
}
}
const channelHexes = this.getSubscribedAuthors();
@@ -422,6 +429,22 @@ class SubscriptionsManager {
? result.items.map((item) => item?.video).filter(Boolean)
: [];
const metadata = result && typeof result.metadata === "object"
? { ...result.metadata }
: {};
if (!metadata.feed) {
metadata.feed = "subscriptions";
}
if (limit) {
metadata.limit = limit;
}
if (reason) {
metadata.reason = reason;
}
const enrichedResult = { ...result, metadata };
if (app?.videosMap instanceof Map) {
videos.forEach((video) => {
if (video && typeof video.id === "string" && video.id) {
@@ -430,10 +453,15 @@ class SubscriptionsManager {
});
}
this.lastResult = result;
this.renderSameGridStyle(result, containerId, { limit, reason });
this.lastResult = enrichedResult;
this.renderSameGridStyle(enrichedResult, containerId, {
limit,
reason,
emptyMessage:
"No playable subscription videos found yet. We'll keep watching for new posts.",
});
this.hasRenderedOnce = true;
return result;
return enrichedResult;
} catch (error) {
userLogger.error(
"[SubscriptionsManager] Failed to run subscriptions feed:",
@@ -587,6 +615,10 @@ class SubscriptionsManager {
? { ...result.metadata }
: {};
if (!metadata.feed) {
metadata.feed = "subscriptions";
}
const limitCandidate = Number(options?.limit);
const limit =
Number.isFinite(limitCandidate) && limitCandidate > 0
@@ -599,10 +631,15 @@ class SubscriptionsManager {
.filter((video) => video && typeof video === "object");
if (!videos.length) {
container.innerHTML = `
<p class="flex justify-center items-center h-full w-full text-center text-muted-strong">
No videos available yet.
</p>`;
const reasonDetail =
typeof metadata.reason === "string" && metadata.reason
? metadata.reason
: "empty";
this.renderEmptyState(container, {
message: options?.emptyMessage,
reason: reasonDetail,
metadata,
});
return;
}
@@ -636,6 +673,39 @@ class SubscriptionsManager {
listView.render(videos, enrichedMetadata);
}
renderEmptyState(container, { message, reason, metadata } = {}) {
if (!container) {
return;
}
const copy =
typeof message === "string" && message.trim()
? message.trim()
: "No playable subscription videos found yet. We'll keep watching for new posts.";
container.innerHTML = getSidebarLoadingMarkup(copy, { showSpinner: false });
if (this.subscriptionListView && this.subscriptionListView.state) {
const currentMetadata =
this.subscriptionListView.state.feedMetadata &&
typeof this.subscriptionListView.state.feedMetadata === "object"
? { ...this.subscriptionListView.state.feedMetadata }
: {};
if (metadata && typeof metadata === "object") {
Object.assign(currentMetadata, metadata);
}
if (reason && typeof reason === "string") {
currentMetadata.reason = reason;
} else if (!currentMetadata.reason) {
currentMetadata.reason = "empty";
}
this.subscriptionListView.state.feedMetadata = currentMetadata;
}
}
getListView(container, app) {
if (this.subscriptionListView) {
return this.subscriptionListView;
+58
View File
@@ -203,6 +203,11 @@ test(
assert.equal(runCalls.length, 1, "engine.run should execute after warm-up");
assert.equal(renderCalls.length, 1, "initial render should occur once");
assert.equal(
renderCalls[0].options.emptyMessage.includes("No playable subscription videos"),
true,
"empty state should provide a descriptive message",
);
nostrService.emit("videos:updated", {
reason: "subscription",
@@ -226,6 +231,11 @@ test(
"video-1",
"updated render should receive fresh videos",
);
assert.equal(
renderCalls[1].options.emptyMessage.includes("No playable subscription videos"),
true,
"subsequent renders should retain the empty state message for metadata",
);
if (typeof manager.unsubscribeFromNostrUpdates === "function") {
manager.unsubscribeFromNostrUpdates();
@@ -257,3 +267,51 @@ test(
},
);
test("renderSameGridStyle shows empty state message", async () => {
const SubscriptionsManager = subscriptions.constructor;
const originalWindow = globalThis.window;
const originalDocument = globalThis.document;
const dom = new JSDOM(
"<!doctype html><div id=\"subscriptionsVideoList\"></div>",
{ url: "https://example.test/" },
);
globalThis.window = dom.window;
globalThis.document = dom.window.document;
const manager = new SubscriptionsManager();
manager.renderSameGridStyle(
{ items: [] },
"subscriptionsVideoList",
{ emptyMessage: "Custom empty state copy." },
);
const container = dom.window.document.getElementById("subscriptionsVideoList");
assert.match(
container.textContent,
/Custom empty state copy\./,
"empty state should render the provided copy",
);
assert.ok(
!container.textContent.includes("Fetching subscriptions"),
"loading spinner should be removed when rendering the empty state",
);
dom.window.close();
if (typeof originalWindow === "undefined") {
delete globalThis.window;
} else {
globalThis.window = originalWindow;
}
if (typeof originalDocument === "undefined") {
delete globalThis.document;
} else {
globalThis.document = originalDocument;
}
});