forked from coracle/flotilla
Update to new thunk stuff
This commit is contained in:
+27
-20
@@ -1,6 +1,6 @@
|
|||||||
import * as nip19 from "nostr-tools/nip19"
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {randomId, poll, uniq, equals} from "@welshman/lib"
|
import {randomId, ifLet, poll, uniq, equals} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
DELETE,
|
DELETE,
|
||||||
REPORT,
|
REPORT,
|
||||||
@@ -38,7 +38,7 @@ import {
|
|||||||
signer,
|
signer,
|
||||||
repository,
|
repository,
|
||||||
publishThunk,
|
publishThunk,
|
||||||
publishThunks,
|
MergedThunk,
|
||||||
profilesByPubkey,
|
profilesByPubkey,
|
||||||
relaySelectionsByPubkey,
|
relaySelectionsByPubkey,
|
||||||
getWriteRelayUrls,
|
getWriteRelayUrls,
|
||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
tagEventForComment,
|
tagEventForComment,
|
||||||
tagEventForQuote,
|
tagEventForQuote,
|
||||||
Router,
|
Router,
|
||||||
|
thunkIsComplete,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import type {Thunk} from "@welshman/app"
|
import type {Thunk} from "@welshman/app"
|
||||||
import {
|
import {
|
||||||
@@ -84,14 +85,20 @@ export const getPubkeyPetname = (pubkey: string) => {
|
|||||||
return display
|
return display
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getThunkError = async (thunk: Thunk) => {
|
export const getThunkError = (thunk: Thunk) =>
|
||||||
const result = await thunk.result
|
new Promise<string>(resolve => {
|
||||||
const [{status, message}] = Object.values(result) as any
|
thunk.subscribe($thunk => {
|
||||||
|
for (const [relay, status] of Object.entries($thunk.status)) {
|
||||||
|
if (status === PublishStatus.Failure) {
|
||||||
|
resolve($thunk.details[relay])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (status !== PublishStatus.Success) {
|
if (thunkIsComplete($thunk)) {
|
||||||
return message
|
resolve("")
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
})
|
||||||
|
|
||||||
export const prependParent = (parent: TrustedEvent | undefined, {content, tags}: EventContent) => {
|
export const prependParent = (parent: TrustedEvent | undefined, {content, tags}: EventContent) => {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
@@ -261,12 +268,10 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
|||||||
relays: [url],
|
relays: [url],
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await thunk.result
|
ifLet(await getThunkError(thunk), error => {
|
||||||
|
|
||||||
if (result[url].status === PublishStatus.Failure) {
|
|
||||||
const message =
|
const message =
|
||||||
socket.auth.details?.replace(/^.*: /, "") ||
|
socket.auth.details?.replace(/^.*: /, "") ||
|
||||||
result[url].message?.replace(/^.*: /, "") ||
|
error?.replace(/^.*: /, "") ||
|
||||||
"join request rejected"
|
"join request rejected"
|
||||||
|
|
||||||
// If it's a strict NIP 29 relay don't worry about requesting access
|
// If it's a strict NIP 29 relay don't worry about requesting access
|
||||||
@@ -274,7 +279,7 @@ export const checkRelayAccess = async (url: string, claim = "") => {
|
|||||||
if (message !== "missing group (`h`) tag") {
|
if (message !== "missing group (`h`) tag") {
|
||||||
return `Failed to join relay (${message})`
|
return `Failed to join relay (${message})`
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkRelayProfile = async (url: string) => {
|
export const checkRelayProfile = async (url: string) => {
|
||||||
@@ -342,13 +347,15 @@ export const sendWrapped = async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const nip59 = Nip59.fromSigner(signer.get()!)
|
const nip59 = Nip59.fromSigner(signer.get()!)
|
||||||
|
|
||||||
return publishThunks(
|
return new MergedThunk(
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
uniq(pubkeys).map(async recipient => ({
|
uniq(pubkeys).map(async recipient =>
|
||||||
event: await nip59.wrap(recipient, stamp(template)),
|
publishThunk({
|
||||||
relays: Router.get().PubkeyInbox(recipient).getUrls(),
|
event: await nip59.wrap(recipient, stamp(template)),
|
||||||
delay,
|
relays: Router.get().PubkeyInbox(recipient).getUrls(),
|
||||||
})),
|
delay,
|
||||||
|
}),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {hash} from "@welshman/lib"
|
import {hash, now} from "@welshman/lib"
|
||||||
import {now} from "@welshman/lib"
|
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
thunks,
|
thunks,
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
deriveProfileDisplay,
|
deriveProfileDisplay,
|
||||||
formatTimestampAsDate,
|
formatTimestampAsDate,
|
||||||
formatTimestampAsTime,
|
formatTimestampAsTime,
|
||||||
|
thunkIsComplete,
|
||||||
} from "@welshman/app"
|
} from "@welshman/app"
|
||||||
import {isMobile} from "@lib/html"
|
import {isMobile} from "@lib/html"
|
||||||
import TapTarget from "@lib/components/TapTarget.svelte"
|
import TapTarget from "@lib/components/TapTarget.svelte"
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
const profile = deriveProfile(event.pubkey)
|
const profile = deriveProfile(event.pubkey)
|
||||||
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
const profileDisplay = deriveProfileDisplay(event.pubkey)
|
||||||
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
|
const [_, colorValue] = colors[parseInt(hash(event.pubkey)) % colors.length]
|
||||||
|
const hideMenuButton = $derived($thunk && !thunkIsComplete($thunk))
|
||||||
|
|
||||||
const reply = () => replyTo(event)
|
const reply = () => replyTo(event)
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
<div class="row-2 ml-10 mt-1">
|
<div class="row-2 ml-10 mt-1">
|
||||||
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right" />
|
<ReactionSummary {url} {event} {onReactionClick} reactionClass="tooltip-right" />
|
||||||
</div>
|
</div>
|
||||||
{#if !isMobile}
|
{#if !isMobile && !hideMenuButton}
|
||||||
<button
|
<button
|
||||||
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
|
class="join absolute right-1 top-1 border border-solid border-neutral text-xs opacity-0 transition-all"
|
||||||
class:group-hover:opacity-100={!isMobile}>
|
class:group-hover:opacity-100={!isMobile}>
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {get} from "svelte/store"
|
import {nth} from "@welshman/lib"
|
||||||
import {PublishStatus} from "@welshman/net"
|
import {PublishStatus} from "@welshman/net"
|
||||||
import {mergeThunks, publishThunk} from "@welshman/app"
|
import {
|
||||||
import type {Thunk, MergedThunk} from "@welshman/app"
|
MergedThunk,
|
||||||
import {throttled} from "@welshman/store"
|
publishThunk,
|
||||||
|
isMergedThunk,
|
||||||
|
thunkIsComplete,
|
||||||
|
thunkHasStatus,
|
||||||
|
} from "@welshman/app"
|
||||||
|
import type {Thunk} from "@welshman/app"
|
||||||
|
import {fade} from "@lib/transition"
|
||||||
import Icon from "@lib/components/Icon.svelte"
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
import Tippy from "@lib/components/Tippy.svelte"
|
import Tippy from "@lib/components/Tippy.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
|
||||||
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
|
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
|
||||||
import {userSettingValues} from "@app/state"
|
import {userSettingValues} from "@app/state"
|
||||||
|
|
||||||
@@ -17,31 +22,34 @@
|
|||||||
|
|
||||||
let {thunk, ...restProps}: Props = $props()
|
let {thunk, ...restProps}: Props = $props()
|
||||||
|
|
||||||
const {Pending, Failure, Timeout} = PublishStatus
|
|
||||||
|
|
||||||
const abort = () => thunk.controller.abort()
|
const abort = () => thunk.controller.abort()
|
||||||
|
|
||||||
const retry = () => {
|
const retry = () => {
|
||||||
thunk = (thunk as any).thunks
|
thunk = isMergedThunk(thunk)
|
||||||
? mergeThunks((thunk as MergedThunk).thunks.map(t => publishThunk(t.request)))
|
? new MergedThunk(thunk.thunks.map(t => publishThunk(t.options)))
|
||||||
: publishThunk((thunk as Thunk).request)
|
: publishThunk(thunk.options)
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = $derived(throttled(300, thunk.status))
|
const statuses = $derived(Object.entries($thunk.status))
|
||||||
const ps = $derived(Object.values($status))
|
const isSending = $derived(thunkHasStatus($thunk, PublishStatus.Sending))
|
||||||
const canCancel = $derived(ps.length === 0 && $userSettingValues.send_delay > 0)
|
const canCancel = $derived(isSending && $userSettingValues.send_delay > 0)
|
||||||
const isFailure = $derived(!canCancel && ps.every(s => [Failure, Timeout].includes(s.status)))
|
const failedUrls = $derived(
|
||||||
const failure = $derived(
|
statuses
|
||||||
Object.entries($status).find(([url, s]) => [Failure, Timeout].includes(s.status)),
|
.filter(([_, status]) => [PublishStatus.Failure, PublishStatus.Timeout].includes(status))
|
||||||
|
.map(nth(1)),
|
||||||
)
|
)
|
||||||
|
|
||||||
let isPending = $state(Object.values(get(thunk.status)).some(s => s.status == Pending))
|
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
|
||||||
|
|
||||||
|
let isPending = $state(thunkHasStatus($thunk, PublishStatus.Pending))
|
||||||
|
|
||||||
|
const showPending = $derived(canCancel || isPending)
|
||||||
|
|
||||||
// Delay updating isPending so users can see that the message is sent
|
// Delay updating isPending so users can see that the message is sent
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
isPending = isPending || ps.some(s => s.status === Pending)
|
isPending = isPending || thunkHasStatus($thunk, PublishStatus.Pending)
|
||||||
|
|
||||||
if (!ps.some(s => s.status === Pending)) {
|
if (!thunkHasStatus($thunk, PublishStatus.Pending)) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isPending = false
|
isPending = false
|
||||||
}, 2000)
|
}, 2000)
|
||||||
@@ -49,30 +57,43 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isFailure && failure}
|
{#if showFailure || showPending}
|
||||||
{@const [url, {message, status}] = failure}
|
<div class="relative" out:fade>
|
||||||
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
<div class="absolute right-0 top-2">
|
||||||
<Tippy
|
{#if showFailure}
|
||||||
class="flex items-center {restProps.class}"
|
{@const url = failedUrls[0]}
|
||||||
component={ThunkStatusDetail}
|
{@const status = $thunk.status[url]}
|
||||||
props={{url, message, status, retry}}
|
{@const message = $thunk.details[url]}
|
||||||
params={{interactive: true}}>
|
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
||||||
{#snippet children()}
|
<Tippy
|
||||||
<span class="flex cursor-pointer items-center gap-1 text-error">
|
class="flex items-center {restProps.class}"
|
||||||
<Icon icon="danger" size={3} />
|
component={ThunkStatusDetail}
|
||||||
<span>Failed to send!</span>
|
props={{url, message, status, retry}}
|
||||||
</span>
|
params={{interactive: true}}>
|
||||||
{/snippet}
|
{#snippet children()}
|
||||||
</Tippy>
|
<span class="flex cursor-pointer items-center gap-1 text-error">
|
||||||
</div>
|
<Icon icon="danger" size={3} />
|
||||||
{:else if canCancel || isPending}
|
<span>Failed to send!</span>
|
||||||
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
</span>
|
||||||
<span class="flex items-center gap-1 {restProps.class}">
|
{/snippet}
|
||||||
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
</Tippy>
|
||||||
<span class="opacity-50">Sending...</span>
|
</div>
|
||||||
{#if canCancel}
|
{:else if canCancel || isPending}
|
||||||
<Button class="link" onclick={abort}>Cancel</Button>
|
<div class="flex justify-end px-1 text-xs {restProps.class}">
|
||||||
|
<span class="flex items-center gap-1 {restProps.class}">
|
||||||
|
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
|
||||||
|
<span class="opacity-50">Sending...</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="underline transition-all"
|
||||||
|
class:link={canCancel}
|
||||||
|
class:opacity-25={!canCancel}
|
||||||
|
onclick={abort}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
+7
-2
@@ -251,7 +251,7 @@ export const getUrlsForEvent = derived([trackerStore, thunks], ([$tracker, $thun
|
|||||||
const urls = Array.from($tracker.getRelays(id))
|
const urls = Array.from($tracker.getRelays(id))
|
||||||
|
|
||||||
for (const thunk of getThunksByEventId().get(id) || []) {
|
for (const thunk of getThunksByEventId().get(id) || []) {
|
||||||
for (const url of thunk.request.relays) {
|
for (const url of thunk.options.relays) {
|
||||||
urls.push(url)
|
urls.push(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -661,7 +661,12 @@ export const deriveOtherRooms = (url: string) =>
|
|||||||
|
|
||||||
// Other utils
|
// Other utils
|
||||||
|
|
||||||
export const encodeRelay = (url: string) => encodeURIComponent(normalizeRelayUrl(url))
|
export const encodeRelay = (url: string) =>
|
||||||
|
encodeURIComponent(
|
||||||
|
normalizeRelayUrl(url)
|
||||||
|
.replace(/^wss:\/\//, "")
|
||||||
|
.replace(/\/$/, ""),
|
||||||
|
)
|
||||||
|
|
||||||
export const decodeRelay = (url: string) => normalizeRelayUrl(decodeURIComponent(url))
|
export const decodeRelay = (url: string) => normalizeRelayUrl(decodeURIComponent(url))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user