mirror of
https://github.com/coracle-social/coracle.git
synced 2024-10-06 11:43:30 +00:00
Working on suspending/resuming
This commit is contained in:
parent
3c60add04f
commit
159306be67
@ -35,6 +35,7 @@
|
||||
|
||||
let menuIcon
|
||||
let scrollY
|
||||
let suspendedSubs = []
|
||||
|
||||
export let url = ""
|
||||
|
||||
@ -47,6 +48,7 @@
|
||||
})
|
||||
|
||||
modal.subscribe($modal => {
|
||||
// Keep scroll position on body, but don't allow scrolling
|
||||
if ($modal) {
|
||||
scrollY = window.scrollY
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {writable} from 'svelte/store'
|
||||
import {find, propEq} from 'ramda'
|
||||
import {notesLoader, notesListener} from "src/state/app"
|
||||
@ -8,13 +8,20 @@
|
||||
|
||||
export let note
|
||||
|
||||
const notes = writable([])
|
||||
let onScroll
|
||||
const notes = writable([note])
|
||||
let loader
|
||||
let listener
|
||||
|
||||
onMount(async () => {
|
||||
const loader = await notesLoader(notes, {ids: [note.id]}, {isInModal: true})
|
||||
const listener = await notesListener(notes, [,
|
||||
{'#e': [note.id]},
|
||||
const opts = {isInModal: true}
|
||||
|
||||
if (note.created_at) {
|
||||
opts.since = note.created_at
|
||||
}
|
||||
|
||||
loader = await notesLoader(notes, {ids: [note.id]}, opts)
|
||||
listener = await notesListener(notes, [
|
||||
{kinds: [1, 5, 7], '#e': [note.id]},
|
||||
// We can't target reaction deletes by e tag, so get them
|
||||
// all so we can support toggling like/flags for our user
|
||||
{kinds: [5], authors: $user ? [$user.pubkey] : []}
|
||||
@ -23,13 +30,16 @@
|
||||
notes.subscribe($notes => {
|
||||
note = find(propEq('id', note.id), $notes) || note
|
||||
})
|
||||
})
|
||||
|
||||
onScroll = loader.onScroll
|
||||
|
||||
return loader.unsub
|
||||
onDestroy(() => {
|
||||
loader?.unsub()
|
||||
listener?.unsub()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={loader?.onScroll} />
|
||||
|
||||
{#if note.pubkey}
|
||||
<Note showEntire note={note} />
|
||||
{#each note.replies as r (r.id)}
|
||||
|
@ -59,13 +59,9 @@
|
||||
return top + height < bodyRect.height
|
||||
}
|
||||
|
||||
return channels.watcher.sub({
|
||||
filter: {
|
||||
limit: 100,
|
||||
kinds: [42, 43, 44],
|
||||
'#e': [room],
|
||||
},
|
||||
cb: e => {
|
||||
return await channels.listener.sub(
|
||||
{limit: 100, kinds: [42, 43, 44], '#e': [room]},
|
||||
e => {
|
||||
switcherFn(e.kind, {
|
||||
42: () => {
|
||||
messages = messages.concat(e)
|
||||
@ -86,7 +82,7 @@
|
||||
44: () => null,
|
||||
})
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const edit = () => {
|
||||
|
@ -1,33 +1,43 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {writable} from 'svelte/store'
|
||||
import {navigate} from "svelte-routing"
|
||||
import Anchor from "src/partials/Anchor.svelte"
|
||||
import Note from "src/partials/Note.svelte"
|
||||
import {relays} from "src/state/nostr"
|
||||
import {notesLoader, notesListener} from "src/state/app"
|
||||
import {notesLoader, notesListener, modal} from "src/state/app"
|
||||
|
||||
const notes = writable([])
|
||||
let onScroll
|
||||
let loader
|
||||
let listener
|
||||
|
||||
const createNote = () => {
|
||||
navigate("/notes/new")
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const loader = await notesLoader(notes, {kinds: [1]}, {showParents: true})
|
||||
const listener = await notesListener(notes, {kinds: [1]})
|
||||
loader = await notesLoader(notes, {kinds: [1]}, {showParents: true})
|
||||
listener = await notesListener(notes, {kinds: [1, 5, 7]})
|
||||
|
||||
onScroll = loader.onScroll
|
||||
// When a modal opens, suspend our subscriptions
|
||||
modal.subscribe(async $modal => {
|
||||
if ($modal) {
|
||||
loader.cursor.stop()
|
||||
listener.unsub()
|
||||
} else {
|
||||
loader.cursor.start()
|
||||
listener = await notesListener(notes, {kinds: [1, 5, 7]})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return () => {
|
||||
loader.unsub()
|
||||
listener.unsub()
|
||||
}
|
||||
onDestroy(() => {
|
||||
loader?.unsub()
|
||||
listener?.unsub()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={onScroll} />
|
||||
<svelte:window on:scroll={loader?.onScroll} />
|
||||
|
||||
<ul class="py-8 flex flex-col gap-2 max-w-xl m-auto">
|
||||
{#each (notes ? $notes : []) as n (n.id)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import {onMount} from 'svelte'
|
||||
import {onMount, onDestroy} from 'svelte'
|
||||
import {writable} from 'svelte/store'
|
||||
import {fly} from 'svelte/transition'
|
||||
import Note from "src/partials/Note.svelte"
|
||||
@ -8,26 +8,26 @@
|
||||
|
||||
export let pubkey
|
||||
|
||||
let user
|
||||
const notes = writable([])
|
||||
let onScroll
|
||||
let user
|
||||
let loader
|
||||
let listener
|
||||
|
||||
$: user = $accounts[pubkey]
|
||||
|
||||
onMount(async () => {
|
||||
const filter = {kinds: [1], authors: [pubkey]}
|
||||
const loader = await notesLoader(notes, filter, {showParents: true})
|
||||
const listener = await notesListener(notes, filter)
|
||||
loader = await notesLoader(notes, {kinds: [1], authors: [pubkey]}, {showParents: true})
|
||||
listener = await notesListener(notes, {kinds: [1, 5, 7], authors: [pubkey]})
|
||||
})
|
||||
|
||||
onScroll = loader.onScroll
|
||||
|
||||
return () => {
|
||||
loader.unsub()
|
||||
listener.unsub()
|
||||
}
|
||||
onDestroy(() => {
|
||||
loader.unsub()
|
||||
listener.unsub()
|
||||
})
|
||||
</script>
|
||||
|
||||
<svelte:window on:scroll={loader?.onScroll} />
|
||||
|
||||
{#if user}
|
||||
<div class="max-w-2xl m-auto flex flex-col gap-4 py-8 px-4">
|
||||
<div class="flex flex-col gap-4" in:fly={{y: 20}}>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import {when, prop, identity, whereEq, reverse, uniq, sortBy, uniqBy, find, last, pluck, groupBy} from 'ramda'
|
||||
import {when, assoc, prop, identity, whereEq, reverse, uniq, sortBy, uniqBy, find, last, pluck, groupBy} from 'ramda'
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {writable, get} from 'svelte/store'
|
||||
import {navigate} from "svelte-routing"
|
||||
import {switcherFn} from 'hurdak/lib/hurdak'
|
||||
import {switcherFn, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
import {getLocalJson, setLocalJson, now, timedelta, sleep} from "src/util/misc"
|
||||
import {user} from 'src/state/user'
|
||||
import {_channels, filterMatches, Cursor, channels, relays, findReplyTo} from 'src/state/nostr'
|
||||
import {filterMatches, Cursor, channels, relays, findReplyTo} from 'src/state/nostr'
|
||||
|
||||
export const modal = writable(null)
|
||||
|
||||
@ -68,7 +68,7 @@ export const annotateNotesChunk = async (chunk, {showParents = false} = {}) => {
|
||||
|
||||
if (showParents && parentIds.length) {
|
||||
// Find parents of replies to provide context
|
||||
const parents = await _channels.getter.all({
|
||||
const parents = await channels.getter.all({
|
||||
kinds: [1],
|
||||
ids: parentIds,
|
||||
})
|
||||
@ -82,12 +82,12 @@ export const annotateNotesChunk = async (chunk, {showParents = false} = {}) => {
|
||||
return chunk
|
||||
}
|
||||
|
||||
const replies = await _channels.getter.all({
|
||||
const replies = await channels.getter.all({
|
||||
kinds: [1],
|
||||
'#e': pluck('id', chunk),
|
||||
})
|
||||
|
||||
const reactions = await _channels.getter.all({
|
||||
const reactions = await channels.getter.all({
|
||||
kinds: [7],
|
||||
'#e': pluck('id', chunk.concat(replies)),
|
||||
})
|
||||
@ -123,6 +123,7 @@ export const notesLoader = async (
|
||||
showParents = false,
|
||||
delta = timedelta(1, 'hours'),
|
||||
isInModal = false,
|
||||
since = 1633046400, // nostr epoch
|
||||
} = {}
|
||||
) => {
|
||||
const cursor = new Cursor(filter, delta)
|
||||
@ -142,8 +143,8 @@ export const notesLoader = async (
|
||||
break
|
||||
}
|
||||
|
||||
// If we've gone back to the network's inception we're done
|
||||
if (cursor.since <= 1633046400) {
|
||||
// Stop if we've gone back far enough
|
||||
if (cursor.since <= since) {
|
||||
break
|
||||
}
|
||||
|
||||
@ -165,6 +166,7 @@ export const notesLoader = async (
|
||||
onScroll()
|
||||
|
||||
return {
|
||||
cursor,
|
||||
onScroll,
|
||||
unsub: () => {
|
||||
cursor.stop()
|
||||
@ -194,8 +196,8 @@ export const notesListener = async (notes, filter) => {
|
||||
reactions: n.reactions.filter(e => !ids.includes(e.id)),
|
||||
}))
|
||||
|
||||
return await _channels.listener.sub(
|
||||
{kinds: [1, 5, 7], since: now()},
|
||||
return await channels.listener.sub(
|
||||
ensurePlural(filter).map(assoc('since', now())),
|
||||
e => switcherFn(e.kind, {
|
||||
1: async () => {
|
||||
const id = findReplyTo(e)
|
||||
@ -216,7 +218,6 @@ export const notesListener = async (notes, filter) => {
|
||||
notes.update($notes => deleteNotes($notes, ids))
|
||||
},
|
||||
7: () => {
|
||||
console.log(e)
|
||||
const id = findReplyTo(e)
|
||||
|
||||
updateNote(id, n => ({...n, reactions: n.reactions.concat(e)}))
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {writable} from 'svelte/store'
|
||||
import {debounce} from 'throttle-debounce'
|
||||
import {relayPool, getPublicKey} from 'nostr-tools'
|
||||
import {last, intersection, uniqBy, prop} from 'ramda'
|
||||
import {first, noop, ensurePlural} from 'hurdak/lib/hurdak'
|
||||
@ -51,25 +50,34 @@ export class Channel {
|
||||
this.name = name
|
||||
this.p = Promise.resolve()
|
||||
}
|
||||
sub(filter, cb, onEose = noop) {
|
||||
this.p = this.p.then(() => {
|
||||
const sub = nostr.sub({filter, cb}, this.name, onEose)
|
||||
async sub(filter, cb, onEose = noop) {
|
||||
// Make sure callers have to wait for the previous sub to be done
|
||||
// before they can get a new one.
|
||||
await this.p
|
||||
|
||||
return () => sub.unsub()
|
||||
let resolve
|
||||
const sub = nostr.sub({filter, cb}, this.name, r => {
|
||||
onEose(r)
|
||||
|
||||
resolve()
|
||||
})
|
||||
|
||||
return this.p
|
||||
this.p = new Promise(r => {
|
||||
resolve = r
|
||||
})
|
||||
|
||||
return sub
|
||||
}
|
||||
all(filter) {
|
||||
/* eslint no-async-promise-executor: 0 */
|
||||
return new Promise(async resolve => {
|
||||
const result = []
|
||||
|
||||
const unsub = await this.sub(
|
||||
const sub = await this.sub(
|
||||
filter,
|
||||
e => result.push(e),
|
||||
r => {
|
||||
unsub()
|
||||
sub.unsub()
|
||||
|
||||
resolve(result)
|
||||
},
|
||||
@ -78,7 +86,7 @@ export class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
export const _channels = {
|
||||
export const channels = {
|
||||
listener: new Channel('listener'),
|
||||
getter: new Channel('getter'),
|
||||
}
|
||||
@ -92,13 +100,13 @@ export class Cursor {
|
||||
this.delta = delta
|
||||
this.since = now() - delta
|
||||
this.until = now()
|
||||
this.unsub = null
|
||||
this.sub = null
|
||||
this.q = []
|
||||
this.p = Promise.resolve()
|
||||
}
|
||||
async start() {
|
||||
if (!this.unsub) {
|
||||
this.unsub = await _channels.getter.sub(
|
||||
if (!this.sub) {
|
||||
this.sub = await channels.getter.sub(
|
||||
this.filter.map(f => ({...f, since: this.since, until: this.until})),
|
||||
e => this.onEvent(e),
|
||||
r => this.onEose(r)
|
||||
@ -106,9 +114,9 @@ export class Cursor {
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
if (this.unsub) {
|
||||
this.unsub()
|
||||
this.unsub = null
|
||||
if (this.sub) {
|
||||
this.sub.unsub()
|
||||
this.sub = null
|
||||
}
|
||||
}
|
||||
restart() {
|
||||
@ -133,83 +141,13 @@ export class Cursor {
|
||||
while (true) {
|
||||
await new Promise(requestAnimationFrame)
|
||||
|
||||
if (!this.unsub) {
|
||||
if (!this.sub) {
|
||||
return this.q.splice(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track who is subscribing, so we don't go over our limit
|
||||
|
||||
const channel = name => {
|
||||
let active = false
|
||||
let promise = Promise.resolve('init')
|
||||
|
||||
const _chan = {
|
||||
sub: params => {
|
||||
if (active) {
|
||||
console.error(`Channel ${name} is already active.`)
|
||||
}
|
||||
|
||||
active = true
|
||||
|
||||
const sub = nostr.sub(params)
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
|
||||
sub.unsub()
|
||||
}
|
||||
},
|
||||
all: filter => {
|
||||
// Wait for any other subscriptions to finish
|
||||
promise = promise.then(() => {
|
||||
return new Promise(resolve => {
|
||||
// Collect results
|
||||
let result = []
|
||||
|
||||
// As long as events are coming in, don't resolve. When
|
||||
// events are no longer streaming, resolve and close the subscription
|
||||
const done = debounce(300, () => {
|
||||
unsub()
|
||||
|
||||
resolve(result)
|
||||
})
|
||||
|
||||
// Create our usbscription, every time we get an event, attempt to complete
|
||||
const unsub = _chan.sub({
|
||||
filter,
|
||||
cb: e => {
|
||||
result.push(e)
|
||||
|
||||
done()
|
||||
},
|
||||
})
|
||||
|
||||
// If our filter doesn't match anything, be sure to resolve the promise
|
||||
setTimeout(done, 1000)
|
||||
})
|
||||
})
|
||||
|
||||
return promise
|
||||
},
|
||||
first: async filter => {
|
||||
return first(await channels.getter.all({...filter, limit: 1}))
|
||||
},
|
||||
last: async filter => {
|
||||
return last(await channels.getter.all({...filter}))
|
||||
},
|
||||
}
|
||||
|
||||
return _chan
|
||||
}
|
||||
|
||||
export const channels = {
|
||||
watcher: channel('main'),
|
||||
getter: channel('getter'),
|
||||
}
|
||||
|
||||
// Augment nostr with some extra methods
|
||||
|
||||
nostr.login = privkey => {
|
||||
|
Loading…
Reference in New Issue
Block a user