Add some loading icons

This commit is contained in:
Jonathan Staab 2022-11-30 20:47:53 -08:00
parent e5a4f86474
commit 7015ed136c
8 changed files with 72 additions and 37 deletions

BIN
package-lock.json generated

Binary file not shown.

View File

@ -30,6 +30,7 @@
"hurdak": "github:ConsignCloud/hurdak", "hurdak": "github:ConsignCloud/hurdak",
"nostr-tools": "github:fiatjaf/nostr-tools#1b798b2", "nostr-tools": "github:fiatjaf/nostr-tools#1b798b2",
"ramda": "^0.28.0", "ramda": "^0.28.0",
"svelte-loading-spinners": "^0.3.4",
"svelte-routing": "^1.6.0", "svelte-routing": "^1.6.0",
"throttle-debounce": "^5.0.0", "throttle-debounce": "^5.0.0",
"vite-plugin-node-polyfills": "^0.5.0" "vite-plugin-node-polyfills": "^0.5.0"

View File

@ -50,10 +50,14 @@
modal.subscribe($modal => { modal.subscribe($modal => {
// Keep scroll position on body, but don't allow scrolling // Keep scroll position on body, but don't allow scrolling
if ($modal) { if ($modal) {
scrollY = window.scrollY
document.body.style.top = `-${scrollY}px` // This is not idempotent, so don't duplicate it
document.body.style.position = `fixed` if (document.body.style.position !== 'fixed') {
scrollY = window.scrollY
document.body.style.top = `-${scrollY}px`
document.body.style.position = `fixed`
}
} else { } else {
document.body.style = '' document.body.style = ''
window.scrollTo(0, scrollY) window.scrollTo(0, scrollY)
@ -77,11 +81,15 @@
<Route path="/chat/new" component={ChatEdit} /> <Route path="/chat/new" component={ChatEdit} />
<Route path="/chat/:room" let:params> <Route path="/chat/:room" let:params>
{#key params.room} {#key params.room}
<ChatRoom room={params.room} /> <ChatRoom {...params} />
{/key} {/key}
</Route> </Route>
<Route path="/chat/:room/edit" component={ChatEdit} /> <Route path="/chat/:room/edit" component={ChatEdit} />
<Route path="/users/:pubkey" component={UserDetail} /> <Route path="/users/:pubkey" let:params>
{#key params.pubkey}
<UserDetail {...params} />
{/key}
</Route>
<Route path="/settings/keys" component={Keys} /> <Route path="/settings/keys" component={Keys} />
<Route path="/settings/relays" component={RelayList} /> <Route path="/settings/relays" component={RelayList} />
<Route path="/settings/profile" component={Profile} /> <Route path="/settings/profile" component={Profile} />

View File

@ -37,11 +37,8 @@
} }
const showParent = async () => { const showParent = async () => {
const notes = await annotateNotes(
await channels.getter.all({kinds: [1, 5, 7], ids: [parentId]})
)
modal.set({note: notes[0]}) modal.set({note: {id: parentId}})
} }
const react = content => { const react = content => {

View File

@ -1,20 +1,28 @@
<script> <script>
import {onMount} from 'svelte' import {onMount} from 'svelte'
import {writable} from 'svelte/store' import {writable} from 'svelte/store'
import {fly} from 'svelte/transition'
import {find, propEq} from 'ramda' import {find, propEq} from 'ramda'
import {Cursor} from "src/state/nostr" import Spinner from 'src/partials/Spinner.svelte'
import {notesListener, modal} from "src/state/app" import {Cursor, channels} from "src/state/nostr"
import {notesListener, annotateNotes, modal} from "src/state/app"
import {user} from "src/state/user" import {user} from "src/state/user"
import Note from 'src/partials/Note.svelte' import Note from 'src/partials/Note.svelte'
export let note export let note
const notes = writable([note]) const notes = writable([])
let cursor let cursor
let listener let listener
onMount(() => { onMount(() => {
cursor = new Cursor({ids: [note.id]}, note.created_at) channels.getter
.all({kinds: [1, 5, 7], ids: [note.id]})
.then(annotateNotes)
.then($notes => {
notes.set($notes)
})
listener = notesListener(notes, [ listener = notesListener(notes, [
{kinds: [1, 5, 7], '#e': [note.id]}, {kinds: [1, 5, 7], '#e': [note.id]},
// We can't target reaction deletes by e tag, so get them // We can't target reaction deletes by e tag, so get them
@ -25,10 +33,6 @@
// Populate our initial empty space // Populate our initial empty space
listener.start() listener.start()
const unsubNotes = notes.subscribe($notes => {
note = find(propEq('id', note.id), $notes)
})
// Unsubscribe when modal closes so that others can re-subscribe sooner // Unsubscribe when modal closes so that others can re-subscribe sooner
const unsubModal = modal.subscribe($modal => { const unsubModal = modal.subscribe($modal => {
cursor?.stop() cursor?.stop()
@ -36,27 +40,30 @@
}) })
return () => { return () => {
unsubNotes()
unsubModal() unsubModal()
} }
}) })
</script> </script>
{#if note.pubkey} {#each $notes as note (note.id)}
<Note showEntire note={note} /> <div n:fly={{y: 20}}>
{#each note.replies as r (r.id)} <Note showEntire note={note} />
<div class="ml-4 border-l border-solid border-medium"> {#each note.replies as r (r.id)}
<Note interactive invertColors isReply note={r} />
{#each r.replies as r2 (r2.id)}
<div class="ml-4 border-l border-solid border-medium"> <div class="ml-4 border-l border-solid border-medium">
<Note interactive invertColors isReply note={r2} /> <Note interactive invertColors isReply note={r} />
{#each r2.replies as r3 (r3.id)} {#each r.replies as r2 (r2.id)}
<div class="ml-4 border-l border-solid border-medium"> <div class="ml-4 border-l border-solid border-medium">
<Note interactive invertColors isReply note={r3} /> <Note interactive invertColors isReply note={r2} />
{#each r2.replies as r3 (r3.id)}
<div class="ml-4 border-l border-solid border-medium">
<Note interactive invertColors isReply note={r3} />
</div>
{/each}
</div> </div>
{/each} {/each}
</div> </div>
{/each} {/each}
</div> </div>
{:else}
<Spinner />
{/each} {/each}
{/if}

View File

@ -134,7 +134,7 @@
<div class="flex gap-4 h-full"> <div class="flex gap-4 h-full">
<div class="sm:ml-56 w-full"> <div class="sm:ml-56 w-full">
<div class="relative"> <div class="relative">
<div class="flex flex-col pt-20 pb-32"> <div class="flex flex-col py-32">
<ul class="p-4 max-h-full flex-grow flex flex-col-reverse" name="messages"> <ul class="p-4 max-h-full flex-grow flex flex-col-reverse" name="messages">
{#each annotatedMessages as m (m.id)} {#each annotatedMessages as m (m.id)}
<li in:fly={{y: 20}} class="py-1"> <li in:fly={{y: 20}} class="py-1">

View File

@ -5,6 +5,7 @@
import {navigate} from "svelte-routing" import {navigate} from "svelte-routing"
import {uniqBy, prop} from 'ramda' import {uniqBy, prop} from 'ramda'
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Spinner from "src/partials/Spinner.svelte"
import Note from "src/partials/Note.svelte" import Note from "src/partials/Note.svelte"
import {relays, Cursor} from "src/state/nostr" import {relays, Cursor} from "src/state/nostr"
import {scroller, annotateNotes, notesListener, modal} from "src/state/app" import {scroller, annotateNotes, notesListener, modal} from "src/state/app"
@ -31,7 +32,7 @@
scroll() scroll()
// When a modal opens, suspend our subscriptions // When a modal opens, suspend our subscriptions
modal.subscribe(async $modal => { const modalUnsub = modal.subscribe(async $modal => {
if ($modal) { if ($modal) {
cursor.stop() cursor.stop()
listener.stop() listener.stop()
@ -40,6 +41,10 @@
listener.start() listener.start()
} }
}) })
return () => {
modalUnsub()
}
}) })
onDestroy(() => { onDestroy(() => {
@ -61,11 +66,10 @@
{/each} {/each}
</li> </li>
{/each} {/each}
<li class="flex justify-center py-12" in:fly={{y: 20}}>
<p>Loading notes...</p>
</li>
</ul> </ul>
<!-- This will always be sitting at the bottom in case infinite scrolling can't keep up -->
<Spinner />
{#if $relays.length > 0} {#if $relays.length > 0}
<div class="fixed bottom-0 right-0 p-8"> <div class="fixed bottom-0 right-0 p-8">

View File

@ -4,7 +4,8 @@
import {uniqBy, prop} from 'ramda' import {uniqBy, prop} from 'ramda'
import {fly} from 'svelte/transition' import {fly} from 'svelte/transition'
import Note from "src/partials/Note.svelte" import Note from "src/partials/Note.svelte"
import {Cursor} from 'src/state/nostr' import Spinner from "src/partials/Spinner.svelte"
import {Cursor, epoch} from 'src/state/nostr'
import {user as currentUser} from 'src/state/user' import {user as currentUser} from 'src/state/user'
import {accounts, scroller, notesListener, modal, annotateNotes} from "src/state/app" import {accounts, scroller, notesListener, modal, annotateNotes} from "src/state/app"
@ -15,12 +16,15 @@
let cursor let cursor
let listener let listener
let scroll let scroll
let interval
let loading = true
let modalUnsub
$: user = $accounts[pubkey] $: user = $accounts[pubkey]
onMount(async () => { onMount(async () => {
cursor = new Cursor({kinds: [1], authors: [pubkey]}) cursor = new Cursor({kinds: [1], authors: [pubkey]})
listener = await notesListener(notes, {kinds: [1, 5, 7], authors: [pubkey]}) listener = await notesListener(notes, [{kinds: [1], authors: [pubkey]}, {kinds: [5, 7]}])
scroll = scroller(cursor, async chunk => { scroll = scroller(cursor, async chunk => {
const annotated = await annotateNotes(chunk, {showParents: true}) const annotated = await annotateNotes(chunk, {showParents: true})
@ -30,8 +34,13 @@
// Populate our initial empty space // Populate our initial empty space
scroll() scroll()
// Track loading based on cursor cutoff date
interval = setInterval(() => {
loading = cursor.since > epoch
}, 1000)
// When a modal opens, suspend our subscriptions // When a modal opens, suspend our subscriptions
modal.subscribe(async $modal => { modalUnsub = modal.subscribe(async $modal => {
if ($modal) { if ($modal) {
cursor.stop() cursor.stop()
listener.stop() listener.stop()
@ -45,7 +54,10 @@
onDestroy(() => { onDestroy(() => {
cursor?.stop() cursor?.stop()
listener?.stop() listener?.stop()
modalUnsub()
clearInterval(interval)
}) })
</script> </script>
<svelte:window on:scroll={scroll} /> <svelte:window on:scroll={scroll} />
@ -81,6 +93,12 @@
</div> </div>
{/each} {/each}
</li> </li>
{:else}
{#if loading}
<li><Spinner /></li>
{:else}
<li class="p-20 text-center" in:fly={{y: 20}}>No notes found.</li>
{/if}
{/each} {/each}
</ul> </ul>
</div> </div>