diff --git a/src/pages/LikeStatsPage.svelte b/src/pages/LikeStatsPage.svelte
index cc31fdd..4ad449a 100644
--- a/src/pages/LikeStatsPage.svelte
+++ b/src/pages/LikeStatsPage.svelte
@@ -8,6 +8,7 @@
let scanInProgress = $derived(progress !== undefined);
let givenLikesUsers: LikeStat[] | undefined = $state();
let receivedLikesUsers: LikeStat[] | undefined = $state();
+ let showLoadMore = $state(false);
let likeStats = new LikeStats();
@@ -23,6 +24,7 @@
givenLikesUsers = result.givenLikes;
receivedLikesUsers = result.receivedLikes;
+ showLoadMore = (result.total > givenLikesUsers.length);
progress = undefined;
} else {
likeStats.abortScan();
@@ -34,6 +36,19 @@
}
}
}
+
+ async function loadMore(e: Event) {
+ e.preventDefault();
+
+ if (!(givenLikesUsers && receivedLikesUsers)) { return }
+
+ showLoadMore = false;
+ let result = await likeStats.loadMore();
+
+ givenLikesUsers.push(...result.givenLikes);
+ receivedLikesUsers.push(...result.receivedLikes);
+ showLoadMore = (result.total > givenLikesUsers.length);
+ }
@@ -63,6 +78,10 @@
+
+
+ Load more
+
{/if}
@@ -96,4 +115,8 @@
min-width: 0;
overflow-x: auto;
}
+
+ .load-more {
+ text-align: center;
+ }
diff --git a/src/services/like_stats.ts b/src/services/like_stats.ts
index 8cfa19f..8c1ec03 100644
--- a/src/services/like_stats.ts
+++ b/src/services/like_stats.ts
@@ -1,10 +1,12 @@
import { atURI, feedPostTime } from '../utils.js';
import { BlueskyAPI, accountAPI } from '../api.js';
-export type LikeStatsResponse = { givenLikes: LikeStat[], receivedLikes: LikeStat[] }
+export type LikeStatsResponse = { givenLikes: LikeStat[], receivedLikes: LikeStat[], total: number }
export type LikeStat = { handle?: string, did?: string, avatar?: string, count: number }
export type LikeStatHash = Record
+const pageSize = 25;
+
export class LikeStats {
scanStartTime: number | undefined;
appView: BlueskyAPI;
@@ -13,6 +15,9 @@ export class LikeStats {
progressPostLikes: number;
onProgress: ((days: number) => void) | undefined
abortController?: AbortController;
+ sortedGiven?: LikeStat[];
+ sortedReceived?: LikeStat[];
+ loadedOffset?: number;
constructor() {
this.appView = new BlueskyAPI('public.api.bsky.app');
@@ -32,26 +37,43 @@ export class LikeStats {
let receivedLikes = await this.fetchReceivedLikes(requestedDays);
let receivedStats = this.sumUpReceivedLikes(receivedLikes);
- let topReceived = this.getTopEntries(receivedStats);
+ let sortedReceived = this.getSortedEntries(receivedStats);
let givenLikes = await fetchGivenLikes;
let givenStats = this.sumUpGivenLikes(givenLikes);
- let topGiven = this.getTopEntries(givenStats);
+ let sortedGiven = this.getSortedEntries(givenStats);
- let profileInfo = await this.appView.getRequest('app.bsky.actor.getProfiles',
- { actors: topGiven.map(x => x.did) },
- { abortSignal: this.abortController!.signal }
- );
+ let topReceived = sortedReceived.slice(0, pageSize);
+ let topGiven = sortedGiven.slice(0, pageSize);
+ let total = Math.min(sortedGiven.length, sortedReceived.length);
- for (let profile of profileInfo.profiles) {
- let user = topGiven.find(x => x.did == profile.did)!;
- user.handle = profile.handle;
- user.avatar = profile.avatar;
- }
+ await this.fetchProfileInfoForGivenLikes(topGiven);
this.scanStartTime = undefined;
+ this.sortedReceived = sortedReceived;
+ this.sortedGiven = sortedGiven;
+ this.loadedOffset = pageSize;
- return { givenLikes: topGiven, receivedLikes: topReceived };
+ return { givenLikes: topGiven, receivedLikes: topReceived, total };
+ }
+
+ async loadMore(): Promise {
+ if (!(this.sortedReceived && this.sortedGiven && this.loadedOffset)) {
+ throw "Initial fetch not executed yet";
+ }
+
+ let nextReceived = this.sortedReceived.slice(this.loadedOffset, this.loadedOffset + pageSize);
+ let nextGiven = this.sortedGiven.slice(this.loadedOffset, this.loadedOffset + pageSize);
+ let total = Math.min(this.sortedGiven.length, this.sortedReceived.length);
+
+ let sharedLength = Math.min(nextReceived.length, nextGiven.length);
+ nextReceived = nextReceived.slice(0, sharedLength);
+ nextGiven = nextGiven.slice(0, sharedLength);
+ this.loadedOffset += sharedLength;
+
+ await this.fetchProfileInfoForGivenLikes(nextGiven);
+
+ return { givenLikes: nextGiven, receivedLikes: nextReceived, total };
}
async fetchGivenLikes(requestedDays: number): Promise {
@@ -157,8 +179,21 @@ export class LikeStats {
return stats;
}
- getTopEntries(counts: LikeStatHash): LikeStat[] {
- return Object.entries(counts).sort(this.sortResults).map(x => x[1]).slice(0, 25);
+ getSortedEntries(counts: LikeStatHash): LikeStat[] {
+ return Object.entries(counts).sort(this.sortResults).map(x => x[1]);
+ }
+
+ async fetchProfileInfoForGivenLikes(given: LikeStat[]) {
+ let profileInfo = await this.appView.getRequest('app.bsky.actor.getProfiles',
+ { actors: given.map(x => x.did) },
+ { abortSignal: this.abortController!.signal }
+ );
+
+ for (let profile of profileInfo.profiles) {
+ let user = given.find(x => x.did == profile.did)!;
+ user.handle = profile.handle;
+ user.avatar = profile.avatar;
+ }
}
resetProgress() {