Fix loading and scrolling

This commit is contained in:
Jon Staab
2025-01-02 15:08:16 -08:00
parent 9e96d5e483
commit f5dced433a
8 changed files with 108 additions and 65 deletions

View File

@@ -22,7 +22,7 @@
const expand = () => pushModal(ContentLinkDetail, {url}, {fullscreen: true})
</script>
<Link external href={url} class="my-2 flex">
<Link external href={url} class="my-2 inline-block">
<div class="overflow-hidden rounded-box leading-[0]">
{#if url.match(/\.(mov|webm|mp4)$/)}
<video controls src={url} class="max-h-96 object-contain object-center">

View File

@@ -27,7 +27,7 @@
</p>
</div>
{:else}
<p class="-mb-3 h-0 text-end text-xs opacity-75">
<p class="mb-3 h-0 text-xs opacity-75">
{formatTimestamp(event.created_at)}
</p>
{/if}

View File

@@ -1,9 +1,9 @@
import {get} from "svelte/store"
import {partition, assoc, now, ago, MONTH} from "@welshman/lib"
import {MESSAGE, DELETE, THREAD, COMMENT} from "@welshman/util"
import {partition, assoc, now} from "@welshman/lib"
import {MESSAGE, THREAD, COMMENT} from "@welshman/util"
import type {Subscription} from "@welshman/net"
import type {AppSyncOpts} from "@welshman/app"
import {subscribe, repository, pull, hasNegentropy} from "@welshman/app"
import {subscribe, load, repository, pull, hasNegentropy} from "@welshman/app"
import {userRoomsByUrl, getUrlsForEvent} from "@app/state"
// Utils
@@ -35,12 +35,12 @@ export const listenForNotifications = () => {
const subs: Subscription[] = []
for (const [url, rooms] of userRoomsByUrl.get()) {
pullConservatively({
load({
relays: [url],
filters: [
{kinds: [THREAD, DELETE], since: ago(MONTH)},
{kinds: [COMMENT], "#K": [String(THREAD)], since: ago(MONTH)},
...Array.from(rooms).map(room => ({kinds: [MESSAGE], "#h": [room], since: ago(MONTH)})),
{kinds: [THREAD], limit: 1},
{kinds: [COMMENT], "#K": [String(THREAD)], limit: 1},
...Array.from(rooms).map(room => ({kinds: [MESSAGE], "#h": [room], limit: 1})),
],
})

View File

@@ -607,8 +607,7 @@ export const userSettingValues = withGetter(
derived(userSettings, $s => $s?.values || defaultSettings),
)
export const getSetting = <T = any>(key: keyof Settings["values"]) =>
userSettingValues.get()[key] as T
export const getSetting = <T>(key: keyof Settings["values"]) => userSettingValues.get()[key] as T
export const userMembership = withGetter(
derived([pubkey, membershipByPubkey], ([$pubkey, $membershipByPubkey]) => {

View File

@@ -5,20 +5,12 @@
import {get, derived} from "svelte/store"
import {dev} from "$app/environment"
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
import {
identity,
sleep,
take,
sortBy,
ago,
now,
HOUR,
WEEK,
Worker,
} from "@welshman/lib"
import {identity, sleep, take, sortBy, ago, now, HOUR, WEEK, MONTH, Worker} from "@welshman/lib"
import type {TrustedEvent} from "@welshman/util"
import {
MESSAGE,
PROFILE,
DELETE,
REACTION,
ZAP_RESPONSE,
FOLLOWS,
@@ -111,13 +103,13 @@
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {
throttle: 3000,
migrate: (events: TrustedEvent[]) => {
if (events.length < 50_000) {
if (events.length < 15_000) {
return events
}
const NEVER_KEEP = 0
const ALWAYS_KEEP = Infinity
const reactionKinds = [REACTION, ZAP_RESPONSE]
const reactionKinds = [REACTION, ZAP_RESPONSE, DELETE]
const metaKinds = [PROFILE, FOLLOWS, RELAYS, INBOX_RELAYS]
const $sessionKeys = new Set(Object.keys(app.sessions.get()))
const $userFollows = new Set(getPubkeyTagValues(getListTags(get(app.userFollows))))
@@ -129,6 +121,9 @@
// No need to keep a record of everyone who follows the current user
if (e.kind === FOLLOWS && !isFollowing) return NEVER_KEEP
// Drop room messages after a month, re-load on demand
if (e.kind === MESSAGE && e.created_at < ago(MONTH)) return NEVER_KEEP
// Always keep stuff by or tagging a signed in user
if ($sessionKeys.has(e.pubkey)) return ALWAYS_KEEP
if (e.tags.some(t => $sessionKeys.has(t[1]))) return ALWAYS_KEEP
@@ -148,7 +143,7 @@
}
return take(
30_000,
10_000,
sortBy(e => -scoreEvent(e), events),
)
},
@@ -193,11 +188,11 @@
// Listen for chats, populate chat-based notifications
let chatsSub: any
derived([pubkey, userInboxRelaySelections], identity).subscribe(
([$pubkey, $userInboxRelaySelections]) => {
derived([pubkey, canDecrypt, userInboxRelaySelections], identity).subscribe(
([$pubkey, $canDecrypt, $userInboxRelaySelections]) => {
chatsSub?.close()
if ($pubkey) {
if ($pubkey && $canDecrypt) {
chatsSub = subscribe({
filters: [
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import {onMount} from "svelte"
import {page} from "$app/stores"
import {now} from "@welshman/lib"
import {ago, WEEK} from "@welshman/lib"
import {GROUPS, MESSAGE, DELETE} from "@welshman/util"
import {subscribe} from "@welshman/app"
import {DELETE, REACTION, GROUPS} from "@welshman/util"
import Page from "@lib/components/Page.svelte"
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
import MenuSpace from "@app/components/MenuSpace.svelte"
@@ -12,11 +12,14 @@
import {pushModal} from "@app/modal"
import {setChecked} from "@app/notifications"
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
import {decodeRelay} from "@app/state"
import {decodeRelay, userRoomsByUrl, THREAD_FILTER, COMMENT_FILTER} from "@app/state"
import {pullConservatively} from "@app/requests"
import {notifications} from "@app/notifications"
const url = decodeRelay($page.params.relay)
const rooms = Array.from($userRoomsByUrl.get(url) || [])
const checkConnection = async () => {
const connectionError = await checkRelayConnection(url)
@@ -43,11 +46,27 @@
onMount(() => {
checkConnection()
const sub = subscribe({
relays: [url],
filters: [{kinds: [GROUPS]}, {kinds: [DELETE, REACTION], since: now()}],
const relays = [url]
const since = ago(WEEK)
// Load all groups for this space to populate navigation
pullConservatively({relays, filters: [{kinds: [GROUPS]}]})
// Load threads and comments
pullConservatively({
relays,
filters: [
{...THREAD_FILTER, since},
{...COMMENT_FILTER, since},
],
})
// Load recent messages for user rooms to help with a quick page transition
pullConservatively({relays, filters: rooms.map(r => ({kinds: [MESSAGE], "#h": [r], since}))})
// Listen for deletes that would apply to messages we already have
const sub = subscribe({relays, filters: [{kinds: [DELETE], since}]})
return () => {
sub.close()
}

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import {nip19} from "nostr-tools"
import {onDestroy} from "svelte"
import {onMount} from "svelte"
import {derived} from "svelte/store"
import {page} from "$app/stores"
import {sleep, ctx} from "@welshman/lib"
import {sleep, now, ctx} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {throttled} from "@welshman/store"
import {createEvent, MESSAGE} from "@welshman/util"
import {formatTimestampAsDate, publishThunk, deriveRelay} from "@welshman/app"
import {feedsFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
import {
formatTimestampAsDate,
createFeedController,
subscribe,
publishThunk,
deriveRelay,
} from "@welshman/app"
import {slide} from "@lib/transition"
import {createScroller, type Scroller} from "@lib/html"
import Icon from "@lib/components/Icon.svelte"
@@ -41,6 +48,8 @@
const url = decodeRelay($page.params.relay)
const relay = deriveRelay(url)
const legacyRoom = room === GENERAL ? "general" : room
const feeds = feedsFromFilter({kinds: [MESSAGE], "#h": [room]})
const events = throttled(
300,
deriveEventsForUrl(url, [
@@ -49,6 +58,14 @@
]),
)
const ctrl = createFeedController({
useWindowing: true,
feed: makeIntersectionFeed(makeRelayFeed(url), ...feeds),
onExhausted: () => {
loading = false
},
})
const assertEvent = (e: any) => e as TrustedEvent
const joinRoom = async () => {
@@ -87,8 +104,9 @@
delay: $userSettingValues.send_delay,
})
let limit = 30
let loading = sleep(5000)
let limit = 100
let loading = true
let unmounted = false
let element: HTMLElement
let scroller: Scroller
let editor: ReturnType<typeof getEditor>
@@ -118,27 +136,39 @@
previousPubkey = pubkey
}
return $elements.reverse().slice(0, limit)
return $elements.reverse()
})
// Sveltekit doesn't set element in onMount for some reason
$: {
if (element) {
scroller = createScroller({
element,
delay: 300,
threshold: 3000,
onScroll: () => {
limit += 30
loading = sleep(5000)
},
})
}
}
onMount(() => {
// Element is frequently not defined. I don't know why
sleep(1000).then(() => {
if (!unmounted) {
scroller = createScroller({
element,
delay: 300,
threshold: 10_000,
onScroll: () => {
limit += 100
onDestroy(() => {
setChecked($page.url.pathname)
scroller?.stop()
if ($events.length - limit < 100) {
ctrl.load(200)
}
},
})
}
})
const sub = subscribe({
relays: [url],
filters: [{kinds: [DELETE, REACTION, MESSAGE], "#h": [room], since: now()}],
})
return () => {
unmounted = true
setChecked($page.url.pathname)
scroller?.stop()
sub.close()
}
})
</script>
@@ -170,7 +200,7 @@
<div
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-auto py-2"
bind:this={element}>
{#each $elements as { type, id, value, showPubkey } (id)}
{#each $elements.slice(0, limit) as { type, id, value, showPubkey } (id)}
{#if type === "date"}
<Divider>{value}</Divider>
{:else}
@@ -180,11 +210,11 @@
{/if}
{/each}
<p class="flex h-10 items-center justify-center py-20">
{#await loading}
{#if loading}
<Spinner loading>Looking for messages...</Spinner>
{:then}
{:else}
<Spinner>End of message history</Spinner>
{/await}
{/if}
</p>
</div>
<ChannelCompose bind:editor {content} {onSubmit} />

View File

@@ -5,7 +5,7 @@
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
import {getListTags, getPubkeyTagValues} from "@welshman/util"
import {throttled} from "@welshman/store"
import {feedsFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {feedFromFilters, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
import {createFeedController, userMutes} from "@welshman/app"
import {createScroller, type Scroller} from "@lib/html"
import {fly} from "@lib/transition"
@@ -21,7 +21,7 @@
import {pushModal} from "@app/modal"
const url = decodeRelay($page.params.relay)
const feeds = feedsFromFilters([THREAD_FILTER, COMMENT_FILTER])
const feed = feedFromFilters([THREAD_FILTER, COMMENT_FILTER])
const threads = deriveEventsForUrl(url, [THREAD_FILTER])
const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
@@ -50,7 +50,7 @@
const ctrl = createFeedController({
useWindowing: true,
feed: makeIntersectionFeed(makeRelayFeed(url), feeds),
feed: makeIntersectionFeed(makeRelayFeed(url), feed),
onExhausted: () => {
loading = false
},