Compare commits

..

1 Commits

5 changed files with 59 additions and 141 deletions
+2 -2
View File
@@ -12,7 +12,7 @@
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import Content from "@app/components/Content.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
import ThunkStatus from "@app/components/ThunkStatus.svelte"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
@@ -123,7 +123,7 @@
</div>
</TapTarget>
{#if isOwn && thunk}
<ThunkStatus showToastOnRetry {thunk} class="mb-2 mr-2" />
<ThunkFailure showToastOnRetry {thunk} class="mb-2 mr-2" />
{/if}
<div class="row-2 z-feature -mt-4 ml-4">
<ReactionSummary {event} {deleteReaction} {createReaction} noTooltip />
+2 -2
View File
@@ -26,7 +26,7 @@
import Icon from "@lib/components/Icon.svelte"
import Link from "@lib/components/Link.svelte"
import Button from "@lib/components/Button.svelte"
import ThunkStatus from "@app/components/ThunkStatus.svelte"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import ReactionSummary from "@app/components/ReactionSummary.svelte"
@@ -127,7 +127,7 @@
<div class:mt-2={showPubkey && event.kind !== MESSAGE}>
<RoomItemContent {url} event={$innerEvent ?? event} />
{#if event.pubkey === $pubkey && thunk}
<ThunkStatus showToastOnRetry {thunk} class="mt-1 flex justify-end" />
<ThunkFailure showToastOnRetry {thunk} class="mt-1 flex justify-end" />
{/if}
</div>
</div>
+39 -3
View File
@@ -1,6 +1,14 @@
<script lang="ts">
import type {Instance} from "tippy.js"
import {stopPropagation} from "svelte/legacy"
import {noop} from "@welshman/lib"
import type {AbstractThunk} from "@welshman/app"
import ThunkStatus from "@app/components/ThunkStatus.svelte"
import {getFailedThunkUrls, getThunkUrlsWithStatus, thunkIsComplete} from "@welshman/app"
import {PublishStatus} from "@welshman/net"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
import ThunkStatusDetail from "@app/components/ThunkStatusDetail.svelte"
interface Props {
thunk: AbstractThunk
@@ -8,7 +16,35 @@
class?: string
}
const props: Props = $props()
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const failedUrls = $derived([
...getFailedThunkUrls($thunk),
...getThunkUrlsWithStatus(PublishStatus.Aborted, $thunk),
])
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
let popover: Instance | undefined = $state()
const hideDetail = () => popover?.hide()
</script>
<ThunkStatus {...props} />
{#if showFailure}
<button
class="flex w-full justify-end px-1 text-xs {restProps.class}"
onclick={stopPropagation(noop)}>
<Tippy
bind:popover
class="flex items-center"
component={ThunkStatusDetail}
props={{thunk, hide: hideDetail, showToastOnRetry}}
params={{interactive: true, maxWidth: "none"}}>
{#snippet children()}
<span class="flex cursor-pointer items-center gap-1 opacity-75">
<Icon icon={Danger} class="text-error" size={3} />
<span>Failed to send!</span>
</span>
{/snippet}
</Tippy>
</button>
{/if}
+10 -108
View File
@@ -1,24 +1,9 @@
<script lang="ts">
import type {Instance} from "tippy.js"
import {stopPropagation} from "svelte/legacy"
import {noop} from "@welshman/lib"
import type {AbstractThunk} from "@welshman/app"
import {
abortThunk,
getFailedThunkUrls,
getThunkUrlsWithStatus,
thunkHasStatus,
thunkIsComplete,
} from "@welshman/app"
import {thunkIsComplete, getFailedThunkUrls, getThunkUrlsWithStatus} from "@welshman/app"
import {PublishStatus} from "@welshman/net"
import Check from "@assets/icons/check.svg?dataurl"
import CheckRead from "@assets/icons/check-read.svg?dataurl"
import Danger from "@assets/icons/danger-triangle.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
import ThunkDeliveryBreakdown from "@app/components/ThunkDeliveryBreakdown.svelte"
type DeliveryState = "sending" | "delivered" | "partial" | "failed"
import ThunkFailure from "@app/components/ThunkFailure.svelte"
import ThunkPending from "@app/components/ThunkPending.svelte"
interface Props {
thunk: AbstractThunk
@@ -28,99 +13,16 @@
const {thunk, showToastOnRetry, ...restProps}: Props = $props()
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk))
const failedUrls = $derived([
...getFailedThunkUrls($thunk),
...getThunkUrlsWithStatus(PublishStatus.Aborted, $thunk),
])
const isComplete = $derived(thunkIsComplete($thunk))
const isSending = $derived(thunkHasStatus(PublishStatus.Sending, $thunk))
const deliveryState = $derived.by((): DeliveryState => {
if (!isComplete) {
return "sending"
}
if (successUrls.length === 0 && failedUrls.length > 0) {
return "failed"
}
if (failedUrls.length > 0) {
return "partial"
}
return "delivered"
})
const statusLabel = $derived(
deliveryState === "sending"
? "Sending..."
: deliveryState === "delivered"
? "Message delivered"
: deliveryState === "partial"
? "Partial delivery"
: "Failed to deliver!",
)
const statusIcon = $derived(
deliveryState === "delivered" ? CheckRead : deliveryState === "partial" ? Check : Danger,
)
const statusIconClass = $derived(
deliveryState === "delivered"
? "text-success"
: deliveryState === "partial"
? "text-warning"
: deliveryState === "failed"
? "text-error"
: "",
)
const showBreakdown = $derived(isComplete)
const breakdownState = $derived(
deliveryState === "delivered" || deliveryState === "partial" || deliveryState === "failed"
? deliveryState
: "delivered",
)
const abort = () => abortThunk(thunk)
let popover: Instance | undefined = $state()
const hideBreakdown = () => popover?.hide()
const showFailure = $derived(thunkIsComplete($thunk) && failedUrls.length > 0)
const showPending = $derived(!thunkIsComplete($thunk))
</script>
<div class="flex w-full justify-end px-1 text-xs {restProps.class}">
{#if deliveryState === "sending"}
<span class="flex items-center gap-1 opacity-50">
<span class="loading loading-spinner mx-1 h-3 w-3 translate-y-px"></span>
<span>{statusLabel}</span>
<button
type="button"
class="underline transition-all"
class:link={isSending}
class:opacity-25={!isSending}
class:pointer-events-none={!isSending}
onclick={stopPropagation(abort)}>
Cancel
</button>
</span>
{:else if showBreakdown}
<button type="button" class="flex items-center" onclick={stopPropagation(noop)}>
<Tippy
bind:popover
class="flex items-center"
component={ThunkDeliveryBreakdown}
props={{thunk, deliveryState: breakdownState, hide: hideBreakdown, showToastOnRetry}}
params={{interactive: true, maxWidth: "none"}}>
{#snippet children()}
<span class="flex cursor-pointer items-center gap-1 opacity-75">
<Icon icon={statusIcon} class={statusIconClass} size={3} />
<span>{statusLabel}</span>
</span>
{/snippet}
</Tippy>
</button>
{/if}
</div>
{#if showFailure}
<ThunkFailure class={restProps.class} {thunk} {showToastOnRetry} />
{:else if showPending}
<ThunkPending class={restProps.class} {thunk} />
{/if}
@@ -18,16 +18,13 @@
import ThunkToast from "@app/components/ThunkToast.svelte"
import {pushToast} from "@app/util/toast"
type DeliveryState = "delivered" | "partial" | "failed"
interface Props {
thunk: AbstractThunk
deliveryState: DeliveryState
hide: () => void
showToastOnRetry?: boolean
}
const {thunk, deliveryState, hide, showToastOnRetry}: Props = $props()
const {thunk, hide, showToastOnRetry}: Props = $props()
const successUrls = $derived(getThunkUrlsWithStatus(PublishStatus.Success, $thunk))
const failedUrls = $derived([
@@ -35,30 +32,13 @@
...getThunkUrlsWithStatus(PublishStatus.Aborted, $thunk),
])
const total = $derived(successUrls.length + failedUrls.length)
const isPartial = $derived(successUrls.length > 0 && failedUrls.length > 0)
const title = $derived(
deliveryState === "delivered"
? "Message delivered"
: deliveryState === "partial"
? `Partial delivery ${successUrls.length}/${total} relays`
: "Failed to deliver!",
isPartial ? `Partial delivery ${successUrls.length}/${total} relays` : "Failed to send!",
)
const titleIconClass = $derived(
deliveryState === "delivered"
? "text-success"
: deliveryState === "partial"
? "text-warning"
: "text-error",
)
const titleIcon = $derived(deliveryState === "delivered" ? CheckCircle : Danger)
const relayMessage = (
url: string,
status: PublishStatus | undefined,
detail: string | undefined,
) => {
const relayMessage = (status: PublishStatus | undefined, detail: string | undefined) => {
if (detail) {
return detail
}
@@ -96,7 +76,7 @@
<div class="card2 bg-alt flex min-w-72 max-w-sm flex-col gap-3 px-4 py-3 shadow-lg">
<div class="flex items-center justify-between gap-2">
<span class="flex items-center gap-2 text-sm font-medium">
<Icon icon={titleIcon} class={titleIconClass} size={4} />
<Icon icon={Danger} class="text-error" size={4} />
{title}
</span>
<button type="button" class="opacity-50 hover:opacity-100" onclick={hide}>
@@ -117,7 +97,7 @@
<Icon icon={Danger} class="mt-0.5 text-error" size={4} />
<div class="min-w-0">
<p class="break-all">{displayRelayUrl(url)}</p>
<p class="text-xs opacity-60">{addPeriod(relayMessage(url, status, detail))}</p>
<p class="text-xs opacity-60">{addPeriod(relayMessage(status, detail))}</p>
</div>
<Button class="link shrink-0 px-1" onclick={stopPropagation(() => retryRelay(url))}>
Retry