mirror of
https://github.com/coracle-social/flotilla.git
synced 2025-12-10 19:07:06 +00:00
Fix loading and scrolling
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
const expand = () => pushModal(ContentLinkDetail, {url}, {fullscreen: true})
|
const expand = () => pushModal(ContentLinkDetail, {url}, {fullscreen: true})
|
||||||
</script>
|
</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]">
|
<div class="overflow-hidden rounded-box leading-[0]">
|
||||||
{#if url.match(/\.(mov|webm|mp4)$/)}
|
{#if url.match(/\.(mov|webm|mp4)$/)}
|
||||||
<video controls src={url} class="max-h-96 object-contain object-center">
|
<video controls src={url} class="max-h-96 object-contain object-center">
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{: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)}
|
{formatTimestamp(event.created_at)}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {partition, assoc, now, ago, MONTH} from "@welshman/lib"
|
import {partition, assoc, now} from "@welshman/lib"
|
||||||
import {MESSAGE, DELETE, THREAD, COMMENT} from "@welshman/util"
|
import {MESSAGE, THREAD, COMMENT} from "@welshman/util"
|
||||||
import type {Subscription} from "@welshman/net"
|
import type {Subscription} from "@welshman/net"
|
||||||
import type {AppSyncOpts} from "@welshman/app"
|
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"
|
import {userRoomsByUrl, getUrlsForEvent} from "@app/state"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
@@ -35,12 +35,12 @@ export const listenForNotifications = () => {
|
|||||||
const subs: Subscription[] = []
|
const subs: Subscription[] = []
|
||||||
|
|
||||||
for (const [url, rooms] of userRoomsByUrl.get()) {
|
for (const [url, rooms] of userRoomsByUrl.get()) {
|
||||||
pullConservatively({
|
load({
|
||||||
relays: [url],
|
relays: [url],
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [THREAD, DELETE], since: ago(MONTH)},
|
{kinds: [THREAD], limit: 1},
|
||||||
{kinds: [COMMENT], "#K": [String(THREAD)], since: ago(MONTH)},
|
{kinds: [COMMENT], "#K": [String(THREAD)], limit: 1},
|
||||||
...Array.from(rooms).map(room => ({kinds: [MESSAGE], "#h": [room], since: ago(MONTH)})),
|
...Array.from(rooms).map(room => ({kinds: [MESSAGE], "#h": [room], limit: 1})),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -607,8 +607,7 @@ export const userSettingValues = withGetter(
|
|||||||
derived(userSettings, $s => $s?.values || defaultSettings),
|
derived(userSettings, $s => $s?.values || defaultSettings),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getSetting = <T = any>(key: keyof Settings["values"]) =>
|
export const getSetting = <T>(key: keyof Settings["values"]) => userSettingValues.get()[key] as T
|
||||||
userSettingValues.get()[key] as T
|
|
||||||
|
|
||||||
export const userMembership = withGetter(
|
export const userMembership = withGetter(
|
||||||
derived([pubkey, membershipByPubkey], ([$pubkey, $membershipByPubkey]) => {
|
derived([pubkey, membershipByPubkey], ([$pubkey, $membershipByPubkey]) => {
|
||||||
|
|||||||
@@ -5,20 +5,12 @@
|
|||||||
import {get, derived} from "svelte/store"
|
import {get, derived} from "svelte/store"
|
||||||
import {dev} from "$app/environment"
|
import {dev} from "$app/environment"
|
||||||
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
|
import {bytesToHex, hexToBytes} from "@noble/hashes/utils"
|
||||||
import {
|
import {identity, sleep, take, sortBy, ago, now, HOUR, WEEK, MONTH, Worker} from "@welshman/lib"
|
||||||
identity,
|
|
||||||
sleep,
|
|
||||||
take,
|
|
||||||
sortBy,
|
|
||||||
ago,
|
|
||||||
now,
|
|
||||||
HOUR,
|
|
||||||
WEEK,
|
|
||||||
Worker,
|
|
||||||
} from "@welshman/lib"
|
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
|
MESSAGE,
|
||||||
PROFILE,
|
PROFILE,
|
||||||
|
DELETE,
|
||||||
REACTION,
|
REACTION,
|
||||||
ZAP_RESPONSE,
|
ZAP_RESPONSE,
|
||||||
FOLLOWS,
|
FOLLOWS,
|
||||||
@@ -111,13 +103,13 @@
|
|||||||
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {
|
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {
|
||||||
throttle: 3000,
|
throttle: 3000,
|
||||||
migrate: (events: TrustedEvent[]) => {
|
migrate: (events: TrustedEvent[]) => {
|
||||||
if (events.length < 50_000) {
|
if (events.length < 15_000) {
|
||||||
return events
|
return events
|
||||||
}
|
}
|
||||||
|
|
||||||
const NEVER_KEEP = 0
|
const NEVER_KEEP = 0
|
||||||
const ALWAYS_KEEP = Infinity
|
const ALWAYS_KEEP = Infinity
|
||||||
const reactionKinds = [REACTION, ZAP_RESPONSE]
|
const reactionKinds = [REACTION, ZAP_RESPONSE, DELETE]
|
||||||
const metaKinds = [PROFILE, FOLLOWS, RELAYS, INBOX_RELAYS]
|
const metaKinds = [PROFILE, FOLLOWS, RELAYS, INBOX_RELAYS]
|
||||||
const $sessionKeys = new Set(Object.keys(app.sessions.get()))
|
const $sessionKeys = new Set(Object.keys(app.sessions.get()))
|
||||||
const $userFollows = new Set(getPubkeyTagValues(getListTags(get(app.userFollows))))
|
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
|
// No need to keep a record of everyone who follows the current user
|
||||||
if (e.kind === FOLLOWS && !isFollowing) return NEVER_KEEP
|
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
|
// Always keep stuff by or tagging a signed in user
|
||||||
if ($sessionKeys.has(e.pubkey)) return ALWAYS_KEEP
|
if ($sessionKeys.has(e.pubkey)) return ALWAYS_KEEP
|
||||||
if (e.tags.some(t => $sessionKeys.has(t[1]))) return ALWAYS_KEEP
|
if (e.tags.some(t => $sessionKeys.has(t[1]))) return ALWAYS_KEEP
|
||||||
@@ -148,7 +143,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return take(
|
return take(
|
||||||
30_000,
|
10_000,
|
||||||
sortBy(e => -scoreEvent(e), events),
|
sortBy(e => -scoreEvent(e), events),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -193,11 +188,11 @@
|
|||||||
// Listen for chats, populate chat-based notifications
|
// Listen for chats, populate chat-based notifications
|
||||||
let chatsSub: any
|
let chatsSub: any
|
||||||
|
|
||||||
derived([pubkey, userInboxRelaySelections], identity).subscribe(
|
derived([pubkey, canDecrypt, userInboxRelaySelections], identity).subscribe(
|
||||||
([$pubkey, $userInboxRelaySelections]) => {
|
([$pubkey, $canDecrypt, $userInboxRelaySelections]) => {
|
||||||
chatsSub?.close()
|
chatsSub?.close()
|
||||||
|
|
||||||
if ($pubkey) {
|
if ($pubkey && $canDecrypt) {
|
||||||
chatsSub = subscribe({
|
chatsSub = subscribe({
|
||||||
filters: [
|
filters: [
|
||||||
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
{kinds: [WRAP], "#p": [$pubkey], since: ago(WEEK, 2)},
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {page} from "$app/stores"
|
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 {subscribe} from "@welshman/app"
|
||||||
import {DELETE, REACTION, GROUPS} from "@welshman/util"
|
|
||||||
import Page from "@lib/components/Page.svelte"
|
import Page from "@lib/components/Page.svelte"
|
||||||
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
import SecondaryNav from "@lib/components/SecondaryNav.svelte"
|
||||||
import MenuSpace from "@app/components/MenuSpace.svelte"
|
import MenuSpace from "@app/components/MenuSpace.svelte"
|
||||||
@@ -12,11 +12,14 @@
|
|||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
import {setChecked} from "@app/notifications"
|
import {setChecked} from "@app/notifications"
|
||||||
import {checkRelayConnection, checkRelayAuth, checkRelayAccess} from "@app/commands"
|
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"
|
import {notifications} from "@app/notifications"
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
|
|
||||||
|
const rooms = Array.from($userRoomsByUrl.get(url) || [])
|
||||||
|
|
||||||
const checkConnection = async () => {
|
const checkConnection = async () => {
|
||||||
const connectionError = await checkRelayConnection(url)
|
const connectionError = await checkRelayConnection(url)
|
||||||
|
|
||||||
@@ -43,11 +46,27 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkConnection()
|
checkConnection()
|
||||||
|
|
||||||
const sub = subscribe({
|
const relays = [url]
|
||||||
relays: [url],
|
const since = ago(WEEK)
|
||||||
filters: [{kinds: [GROUPS]}, {kinds: [DELETE, REACTION], since: now()}],
|
|
||||||
|
// 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 () => {
|
return () => {
|
||||||
sub.close()
|
sub.close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {nip19} from "nostr-tools"
|
import {nip19} from "nostr-tools"
|
||||||
import {onDestroy} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {page} from "$app/stores"
|
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 type {TrustedEvent, EventContent} from "@welshman/util"
|
||||||
import {throttled} from "@welshman/store"
|
import {throttled} from "@welshman/store"
|
||||||
import {createEvent, MESSAGE} from "@welshman/util"
|
import {feedsFromFilter, makeIntersectionFeed, makeRelayFeed} from "@welshman/feeds"
|
||||||
import {formatTimestampAsDate, publishThunk, deriveRelay} from "@welshman/app"
|
import {createEvent, MESSAGE, DELETE, REACTION} from "@welshman/util"
|
||||||
|
import {
|
||||||
|
formatTimestampAsDate,
|
||||||
|
createFeedController,
|
||||||
|
subscribe,
|
||||||
|
publishThunk,
|
||||||
|
deriveRelay,
|
||||||
|
} from "@welshman/app"
|
||||||
import {slide} from "@lib/transition"
|
import {slide} from "@lib/transition"
|
||||||
import {createScroller, type Scroller} from "@lib/html"
|
import {createScroller, type Scroller} from "@lib/html"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
@@ -41,6 +48,8 @@
|
|||||||
const url = decodeRelay($page.params.relay)
|
const url = decodeRelay($page.params.relay)
|
||||||
const relay = deriveRelay(url)
|
const relay = deriveRelay(url)
|
||||||
const legacyRoom = room === GENERAL ? "general" : room
|
const legacyRoom = room === GENERAL ? "general" : room
|
||||||
|
const feeds = feedsFromFilter({kinds: [MESSAGE], "#h": [room]})
|
||||||
|
|
||||||
const events = throttled(
|
const events = throttled(
|
||||||
300,
|
300,
|
||||||
deriveEventsForUrl(url, [
|
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 assertEvent = (e: any) => e as TrustedEvent
|
||||||
|
|
||||||
const joinRoom = async () => {
|
const joinRoom = async () => {
|
||||||
@@ -87,8 +104,9 @@
|
|||||||
delay: $userSettingValues.send_delay,
|
delay: $userSettingValues.send_delay,
|
||||||
})
|
})
|
||||||
|
|
||||||
let limit = 30
|
let limit = 100
|
||||||
let loading = sleep(5000)
|
let loading = true
|
||||||
|
let unmounted = false
|
||||||
let element: HTMLElement
|
let element: HTMLElement
|
||||||
let scroller: Scroller
|
let scroller: Scroller
|
||||||
let editor: ReturnType<typeof getEditor>
|
let editor: ReturnType<typeof getEditor>
|
||||||
@@ -118,27 +136,39 @@
|
|||||||
previousPubkey = pubkey
|
previousPubkey = pubkey
|
||||||
}
|
}
|
||||||
|
|
||||||
return $elements.reverse().slice(0, limit)
|
return $elements.reverse()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sveltekit doesn't set element in onMount for some reason
|
onMount(() => {
|
||||||
$: {
|
// Element is frequently not defined. I don't know why
|
||||||
if (element) {
|
sleep(1000).then(() => {
|
||||||
scroller = createScroller({
|
if (!unmounted) {
|
||||||
element,
|
scroller = createScroller({
|
||||||
delay: 300,
|
element,
|
||||||
threshold: 3000,
|
delay: 300,
|
||||||
onScroll: () => {
|
threshold: 10_000,
|
||||||
limit += 30
|
onScroll: () => {
|
||||||
loading = sleep(5000)
|
limit += 100
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDestroy(() => {
|
if ($events.length - limit < 100) {
|
||||||
setChecked($page.url.pathname)
|
ctrl.load(200)
|
||||||
scroller?.stop()
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
@@ -170,7 +200,7 @@
|
|||||||
<div
|
<div
|
||||||
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-auto py-2"
|
class="scroll-container -mt-2 flex flex-grow flex-col-reverse overflow-auto py-2"
|
||||||
bind:this={element}>
|
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"}
|
{#if type === "date"}
|
||||||
<Divider>{value}</Divider>
|
<Divider>{value}</Divider>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -180,11 +210,11 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
<p class="flex h-10 items-center justify-center py-20">
|
<p class="flex h-10 items-center justify-center py-20">
|
||||||
{#await loading}
|
{#if loading}
|
||||||
<Spinner loading>Looking for messages...</Spinner>
|
<Spinner loading>Looking for messages...</Spinner>
|
||||||
{:then}
|
{:else}
|
||||||
<Spinner>End of message history</Spinner>
|
<Spinner>End of message history</Spinner>
|
||||||
{/await}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ChannelCompose bind:editor {content} {onSubmit} />
|
<ChannelCompose bind:editor {content} {onSubmit} />
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
|
import {sortBy, min, nthEq, sleep} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||||
import {throttled} from "@welshman/store"
|
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 {createFeedController, userMutes} from "@welshman/app"
|
||||||
import {createScroller, type Scroller} from "@lib/html"
|
import {createScroller, type Scroller} from "@lib/html"
|
||||||
import {fly} from "@lib/transition"
|
import {fly} from "@lib/transition"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
import {pushModal} from "@app/modal"
|
import {pushModal} from "@app/modal"
|
||||||
|
|
||||||
const url = decodeRelay($page.params.relay)
|
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 threads = deriveEventsForUrl(url, [THREAD_FILTER])
|
||||||
const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
|
const comments = deriveEventsForUrl(url, [COMMENT_FILTER])
|
||||||
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
const mutedPubkeys = getPubkeyTagValues(getListTags($userMutes))
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
const ctrl = createFeedController({
|
const ctrl = createFeedController({
|
||||||
useWindowing: true,
|
useWindowing: true,
|
||||||
feed: makeIntersectionFeed(makeRelayFeed(url), feeds),
|
feed: makeIntersectionFeed(makeRelayFeed(url), feed),
|
||||||
onExhausted: () => {
|
onExhausted: () => {
|
||||||
loading = false
|
loading = false
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user