Abort feed when updating opts

This commit is contained in:
Jon Staab 2024-05-08 14:21:47 -07:00
parent 324045a1cb
commit 5a813e394e
18 changed files with 83 additions and 61 deletions

View File

@ -24,6 +24,7 @@
- [x] Introduce new in-memory relay
- [x] Re-work feed controls
- [x] Re-work utility library
- [x] Make buttons, chips, and inputs sleeker
# 0.4.4

View File

@ -1,5 +1,4 @@
<script lang="ts">
import {equals} from 'ramda'
import {now} from "@welshman/lib"
import {PublishStatus} from "@welshman/net"
import {quantify, seconds} from "hurdak"
@ -98,7 +97,7 @@
<div class="relative">
<div class="flex">
<Input
class="h-7 !rounded !border-tinted-700 !bg-neutral-800 !px-2 py-px text-tinted-200 outline-none"
class="border-tinted-700 bg-neutral-800 py-px text-tinted-200 outline-none"
on:blur={onSearchBlur}
on:keydown={onSearchKeydown}
bind:element={searchInput}

View File

@ -52,9 +52,10 @@
}
}
const update = async opts => {
const onChange = async opts => {
limit = 0
feed = opts.feed
Storage.setJson("hideReplies", opts.shouldHideReplies)
start(opts)
if (feedLoader.compiler.canCompile(opts.feed)) {
@ -66,10 +67,7 @@
}
}
$: {
update(opts)
Storage.setJson("hideReplies", opts.shouldHideReplies)
}
onChange(opts)
onMount(() => {
const scroller = createScroller(loadMore, {element})
@ -79,7 +77,7 @@
</script>
{#if showControls}
<FeedControls {address} bind:opts />
<FeedControls {opts} {address} {onChange} />
{/if}
<FlexColumn xl bind:element>

View File

@ -20,6 +20,7 @@
} from "src/engine"
export let opts
export let onChange
export let address = null
const openListMenu = () => {
@ -39,13 +40,13 @@
}
const toggleReplies = () => {
opts = {...opts, shouldHideReplies: !opts.shouldHideReplies}
onChange({...opts, shouldHideReplies: !opts.shouldHideReplies})
}
const getSearch = definition => (getFeedArgs(definition)?.find(isSearchFeed)?.[1] as string) || ""
const setFeedDefinition = definition => {
opts = {...opts, feed: definition}
onChange({...opts, feed: definition})
search = getSearch(definition)
closeListMenu()
closeForm()
@ -103,8 +104,9 @@
<div class="flex justify-between">
<Select
dark
value={subFeeds.find(isScopeFeed)?.[1] || null}
class="hidden h-7 bg-tinted-700 text-neutral-200 sm:block">
class="hidden bg-tinted-700 sm:block">
<option value={Scope.Follows}>Follows</option>
<option value={Scope.Network}>Network</option>
<option value={null}>Global</option>
@ -112,27 +114,28 @@
<div class="flex flex-grow items-center justify-end gap-2">
<div class="flex">
<Input
class="hidden h-7 rounded-r-none bg-neutral-900 xs:block"
dark
class="hidden rounded-r-none xs:block"
on:input={onSearchBlur}
bind:value={search}>
<div slot="after" class="hidden text-white xs:block">
<i class="fa fa-search" />
</div>
</Input>
<Anchor button low class="h-7 border-none xs:rounded-l-none" on:click={openForm}>
<Anchor button low class="border-none xs:rounded-l-none" on:click={openForm}>
Filters ({feed.definition.length - 1})
</Anchor>
</div>
<div class="float-right flex h-8 items-center justify-end gap-2">
{#if opts.shouldHideReplies}
<Anchor button low class="h-7 border-none opacity-50" on:click={toggleReplies}
<Anchor button low class="border-none opacity-50" on:click={toggleReplies}
>Replies</Anchor>
{:else}
<Anchor button accent class="h-7 border-none" on:click={toggleReplies}>Replies</Anchor>
<Anchor button accent class="border-none" on:click={toggleReplies}>Replies</Anchor>
{/if}
<div class="relative lg:hidden">
<div
class="flex h-7 w-6 cursor-pointer items-center justify-center rounded bg-neutral-700 text-center text-neutral-50 transition-colors hover:bg-neutral-600"
class="flex h-8 w-6 cursor-pointer items-center justify-center rounded bg-neutral-700 text-center text-neutral-50 transition-colors hover:bg-neutral-600"
on:click={openListMenu}>
<i class="fa fa-sm fa-ellipsis-v" />
</div>

View File

@ -33,7 +33,7 @@
</div>
<QRCode code={zap.invoice} onClick={collect}>
<div slot="below" let:copy class="flex gap-1">
<Input value={zap.invoice} wrapperClass="flex-grow">
<Input value={zap.invoice} class="flex-grow">
<button slot="after" class="fa fa-copy" on:click={copy} />
</Input>
{#await getLightningImplementation()}

View File

@ -48,6 +48,7 @@ export class FeedLoader {
done = false
loader: Promise<Loader>
feedLoader: CoreFeedLoader<Rumor>
controller = new AbortController()
notes = writable<DisplayEvent[]>([])
parents = new Map<string, DisplayEvent>()
reposts = new Map<string, Event[]>()
@ -60,6 +61,8 @@ export class FeedLoader {
this.feedLoader = new CoreFeedLoader({
...baseFeedLoader.options,
request: async ({relays, filters, onEvent}) => {
const signal = this.controller.signal
// Default to note kinds
filters = filters?.map(filter => ({kinds: noteKinds, ...filter})) || []
@ -73,10 +76,10 @@ export class FeedLoader {
// Use relays specified in feeds
if (relays?.length > 0) {
promises.push(load({filters, relays, tracker, onEvent}))
promises.push(load({filters, relays, tracker, onEvent, signal}))
} else {
if (!this.opts.skipCache) {
promises.push(load({filters, relays: [LOCAL_RELAY_URL], tracker, onEvent}))
promises.push(load({filters, relays: [LOCAL_RELAY_URL], tracker, onEvent, signal}))
}
if (!this.opts.skipNetwork) {
@ -87,7 +90,7 @@ export class FeedLoader {
}
for (const {relay, filters} of selections) {
promises.push(load({filters, relays: [relay], tracker, onEvent}))
promises.push(load({filters, relays: [relay], tracker, onEvent, signal}))
}
}
}
@ -100,11 +103,17 @@ export class FeedLoader {
// Public api
start = (opts: Partial<FeedOpts>) => {
const controller = new AbortController()
Object.assign(this.opts, opts)
this.loader = this.feedLoader.getLoader(this.opts.feed, {
onEvent: batch(300, async events => {
const keep = await this.discardEvents(events)
if (controller.signal.aborted) {
return
}
const keep = this.discardEvents(events)
if (this.opts.shouldLoadParents) {
this.loadParents(keep)
@ -121,6 +130,8 @@ export class FeedLoader {
// Clear everything out
this.notes.set([])
this.controller.abort()
this.controller = controller
}
subscribe = f => this.notes.subscribe(f)
@ -129,7 +140,7 @@ export class FeedLoader {
// Event selection, deferral, and parent loading
discardEvents = async events => {
discardEvents = events => {
let strict = true
// Be more tolerant when looking at communities
@ -186,14 +197,20 @@ export class FeedLoader {
return true
})
const {signal} = this.controller
const selections = hints.merge(notesWithParent.map(hints.EventParents)).getSelections()
for (const {relay, values} of selections) {
load({
filters: getIdFilters(values),
signal: this.controller.signal,
relays: this.opts.skipPlatform ? [relay] : forcePlatformRelays([relay]),
onEvent: batch(100, async events => {
for (const e of await this.discardEvents(events)) {
if (signal.aborted) {
return
}
for (const e of this.discardEvents(events)) {
for (const k of getIdAndAddress(e)) {
this.parents.set(k, e)
}
@ -215,7 +232,13 @@ export class FeedLoader {
return parents.length === 0 || parents.some(k => this.parents.has(k))
}, notes)
setTimeout(() => this.addToFeed(defer), 3000)
const {signal} = this.controller
setTimeout(() => {
if (!signal.aborted) {
this.addToFeed(defer)
}
}, 3000)
return ok
}

View File

@ -77,7 +77,7 @@
<p class="text-center py-8">You haven't yet joined any groups.</p>
{/each}
<div class="mb-2 border-b border-solid border-neutral-600 pt-2" />
<Input bind:value={q} type="text" wrapperClass="flex-grow" placeholder="Search groups">
<Input bind:value={q} type="text" class="flex-grow" placeholder="Search groups">
<i slot="before" class="fa-solid fa-search" />
</Input>
{#each otherGroups as group (group.address)}

View File

@ -136,8 +136,7 @@
<div class="flex">
<Input
bind:value={username}
class="rounded-r-none"
wrapperClass="flex-grow"
class="flex-grow rounded-r-none"
placeholder="Username">
<i slot="before" class="fa fa-user-astronaut" />
</Input>
@ -146,8 +145,7 @@
defaultOptions={handlers}
getKey={prop("domain")}
termToItem={objOf("domain")}
inputClass="rounded-l-none border-l-0"
inputWrapperClass="flex-grow"
inputClass="rounded-l-none border-l-0 flex-grow"
search={() => handlers}>
<i slot="before" class="fa fa-at relative top-[2px]" />
<span slot="item" let:item>{item.domain}</span>

View File

@ -42,7 +42,7 @@
</div>
<p>Help people recognize you by setting up your profile.</p>
<Field label="Your Name">
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={profile.name}>
<Input type="text" name="name" class="flex-grow" bind:value={profile.name}>
<i slot="before" class="fa-solid fa-user-astronaut" />
</Input>
</Field>

View File

@ -202,7 +202,7 @@
<Input
bind:value={q}
type="text"
wrapperClass="flex-grow"
class="flex-grow"
placeholder="Search relays or add a custom url">
<i slot="before" class="fa-solid fa-search" />
</Input>

View File

@ -35,13 +35,13 @@
</div>
<div class="flex w-full flex-col gap-8">
<Field label="Username">
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={values.name}>
<Input type="text" name="name" class="flex-grow" bind:value={values.name}>
<i slot="before" class="fa-solid fa-user-astronaut" />
</Input>
<div slot="info">In most clients, this image will be shown on your profile page.</div>
</Field>
<Field label="NIP-05 Identifier">
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={values.nip05}>
<Input type="text" name="name" class="flex-grow" bind:value={values.nip05}>
<i slot="before" class="fa-solid fa-user-check" />
</Input>
<div slot="info">
@ -50,7 +50,7 @@
</div>
</Field>
<Field label="Lightning address">
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={values.lud16}>
<Input type="text" name="name" class="flex-grow" bind:value={values.lud16}>
<i slot="before" class="fa-solid fa-bolt" />
</Input>
<div slot="info">
@ -59,7 +59,7 @@
</div>
</Field>
<Field label="Website">
<Input type="text" name="name" wrapperClass="flex-grow" bind:value={values.website}>
<Input type="text" name="name" class="flex-grow" bind:value={values.website}>
<i slot="before" class="fa-solid fa-link" />
</Input>
<div slot="info">Enter any url where people can find out more about you.</div>

View File

@ -38,7 +38,9 @@
</div>
<div class="flex w-full flex-col gap-8">
<Field label="Default zap amount">
<Input bind:value={settings.default_zap} />
<Input bind:value={settings.default_zap}>
<i slot="before" class="fa fa-bolt" />
</Input>
<p slot="info">The default amount of sats to use when sending a lightning tip.</p>
</Field>
<Field label="Platform zap split">
@ -58,7 +60,7 @@
<strong>Max relays per request</strong>
<div>{settings.relay_limit} relays</div>
</div>
<Input type="range" bind:value={settings.relay_limit} min={1} max={30} parse={parseInt} />
<Input type="range" class="bg-transparent" bind:value={settings.relay_limit} min={1} max={30} parse={parseInt} />
<p slot="info">
This controls how many relays to max out at when loading feeds and event context. More is
faster, but will require more bandwidth and processing power.

View File

@ -33,11 +33,7 @@ export const projections = new Worker<Event>({
})
projections.addGlobalHandler(event => {
const kinds = [
Kind.Delete,
Kind.Feed,
Kind.ListBookmarks,
]
const kinds = [Kind.Delete, Kind.Feed, Kind.ListBookmarks]
if (kinds.includes(event.kind)) {
repository.publish(event)
@ -221,7 +217,7 @@ export const createAndPublish = async ({
const template = createEvent(kind, {content, tags})
const event = await sign(template, {anonymous, sk})
return publish({event, relays, timeout, verb})
return publish({event, relays, verb, signal: AbortSignal.timeout(timeout)})
}
setInterval(() => {

View File

@ -18,7 +18,6 @@
export let danger = false
export let circle = false
export let underline = false
export let short = false
export let tall = false
export let grow = false
export let style = null
@ -44,10 +43,10 @@
button,
"aspect-square flex justify-center items-center rounded-full !p-0": circle,
"aspect-square flex justify-center items-center": square,
"h-7": short,
"w-7": short && circle,
"h-10": tall,
"w-10": tall && circle,
"h-7": button && !tall,
"w-7": button && !tall && circle,
"h-10": button && tall,
"w-10": button && tall && circle,
"flex-grow": grow,
})

View File

@ -55,7 +55,7 @@
<div class="flex gap-2">
{#if icon}
<Input type="text" wrapperClass="flex-grow" bind:value placeholder="https://">
<Input type="text" class="flex-grow" bind:value placeholder="https://">
<i slot="before" class={`fa fa-${icon}`} />
</Input>
{/if}

View File

@ -3,19 +3,18 @@
import {identity} from "ramda"
export let initialValue: string | number = ""
export let wrapperClass = ""
export let value = initialValue
export let element = null
export let hideBefore = false
export let hideAfter = false
export let format: (x: any) => string = identity
export let parse: (x: string) => any = identity
export let dark = false
const showBefore = $$slots.before && !hideBefore
const showAfter = $$slots.after && !hideAfter
const className = cx(
$$props.class,
"outline-none rounded shadow-inset py-2 px-4 w-full placeholder:text-neutral-400 text-black",
"outline-none px-3 w-full placeholder:text-neutral-400 h-7 bg-transparent",
{"pl-10": showBefore, "pr-10": showAfter},
)
@ -26,7 +25,11 @@
$: inputValue = format(value)
</script>
<div class={cx(wrapperClass, "relative")}>
<div
class={cx($$props.class, "shadow-inset relative rounded h-7 overflow-hidden", {
"bg-neutral-900 text-neutral-100": dark,
"bg-white dark:text-neutral-900": !dark,
})}>
<input
{...$$props}
class={className}
@ -39,15 +42,14 @@
on:input
on:keydown />
{#if showBefore}
<div class="absolute left-0 top-2 flex items-center gap-2 px-4 text-black opacity-75">
<div class="absolute left-0 top-0 flex items-center gap-2 px-3 opacity-75 h-7">
<div>
<slot name="before" />
</div>
</div>
{/if}
{#if showAfter}
<div
class="absolute right-0 top-2 m-px flex items-center gap-2 rounded-full px-4 text-black opacity-75">
<div class="absolute right-0 top-0 m-px flex items-center gap-2 rounded-full px-3 opacity-75 h-7">
<div>
<slot name="after" />
</div>

View File

@ -9,7 +9,6 @@
export let value = null
export let onChange = null
export let inputClass = ""
export let inputWrapperClass = ""
export let placeholder = ""
export let delimiters = []
export let search = null
@ -134,7 +133,6 @@
<div>
<Input
class={cx(inputClass, "cursor-text text-black outline-0")}
wrapperClass={inputWrapperClass}
{autofocus}
{placeholder}
bind:value={term}

View File

@ -3,15 +3,18 @@
export let value
export let onChange = null
export let wrapperClass = ""
export let dark = false
const className = cx($$props.class, "rounded shadow-inset px-4 w-full cursor-pointer", {
const className = cx("px-3 w-full bg-transparent h-7", {
"pl-10": $$slots.before,
"pr-10": $$slots.after,
})
</script>
<div class={cx(wrapperClass, "relative")}>
<div class={cx($$props.class, "rounded relative shadow-inset cursor-pointer h-7", {
"bg-neutral-900 text-neutral-100": dark,
"bg-neutral-100 text-neutral-900": !dark,
})}>
<select {...$$props} class={className} bind:value on:change={() => onChange(value)}>
<slot />
</select>