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() {