Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154<script lang="ts"> import BellMinus from "$icon/bell-minus"; import BellPlus from "$icon/bell-plus"; import TriangleAlert from "$icon/triangle-alert"; import { getAuth } from "#lib/auth.svelte.ts"; import { createBobbinClient } from "#lib/api/client.ts"; import { ENTITY_SUBSCRIBED, getEntitySubscription, setEntitySubscribed } from "#lib/api/graph.ts"; import Button from "#lib/components/ui/Button.svelte"; import DropdownItem from "#lib/components/ui/DropdownItem.svelte"; import ToggleFaces from "#lib/components/ui/ToggleFaces.svelte"; import Tooltip from "#lib/components/ui/Tooltip.svelte"; import { resetFailureSoon } from "#lib/optimistic.svelte.ts";
interface Props { entityUri: string; bobbinUrl?: string; variant?: "button" | "block" | "menuitem"; // true for the ticket's author and the repo's owner, who get notices without a subscription record notifiedByDefault?: boolean; }
let { entityUri, bobbinUrl, variant = "button", notifiedByDefault = false }: Props = $props();
const auth = getAuth();
let subscribed = $state(false); let known = $state(false); let toggling = $state(false); let rkey = $state<string | null>(null); let error = $state<string | null>(null);
$effect(() => { const did = auth.currentDid; if (!bobbinUrl || !did) { subscribed = notifiedByDefault; rkey = null; known = true; return; } let cancelled = false; known = false; error = null; getEntitySubscription(createBobbinClient({ serviceUrl: bobbinUrl }), did, entityUri) .then((found) => { if (cancelled) return; rkey = found?.rkey ?? null; subscribed = found ? !found.ignore : notifiedByDefault; known = true; }) .catch(() => { if (cancelled) return; // subscribing re-checks before it writes, so the button can stay usable known = true; error = "Could not load subscription state"; }); return () => { cancelled = true; }; });
// a subscribe or unmute from elsewhere on the page comes in as this event $effect(() => { const onSubscribed = (event: Event) => { const detail = (event as CustomEvent<{ entityUri: string; rkey: string }>).detail; if (detail.entityUri !== entityUri) return; subscribed = true; rkey = detail.rkey; }; window.addEventListener(ENTITY_SUBSCRIBED, onSubscribed); return () => window.removeEventListener(ENTITY_SUBSCRIBED, onSubscribed); });
resetFailureSoon( () => error !== null, () => (error = null) );
// unsubscribing sets ignore on the record, it never deletes it const toggle = async () => { const agent = auth.agent; const did = auth.currentDid; if (!agent || !did || !bobbinUrl || !known || toggling) return; const wasSubscribed = subscribed; const wasRkey = rkey; error = null; toggling = true; subscribed = !wasSubscribed; try { const ctx = createBobbinClient({ serviceUrl: bobbinUrl }); rkey = await setEntitySubscribed(agent, ctx, did, entityUri, !wasSubscribed); } catch { subscribed = wasSubscribed; rkey = wasRkey; error = "Failed to update subscription"; } finally { toggling = false; } };</script>
{#if auth.currentDid && variant === "menuitem"} <DropdownItem icon={error ? TriangleAlert : subscribed ? BellMinus : BellPlus} danger={Boolean(error)} disabled={!known || toggling} onclick={toggle} data-testid="toggleSubscription" data-subscribed={subscribed} data-pending={toggling} > {error ?? (subscribed ? "Unsubscribe" : "Subscribe")} </DropdownItem>{:else if auth.currentDid} <Tooltip content={error ?? ""} disabled={!error} class={variant === "block" ? "w-full" : undefined} > <Button variant={error ? "danger" : "default"} class={variant === "block" ? "w-full" : "max-sm:w-8 max-sm:px-0 sm:min-w-30"} disabled={!known} aria-busy={toggling} onclick={toggle} data-testid="toggleSubscription" data-subscribed={subscribed} data-pending={toggling} > <ToggleFaces on={subscribed} failed={Boolean(error)} offLabel="Subscribe" onLabel="Unsubscribe" labelClass="max-sm:hidden" > {#snippet off(cls)} <BellPlus class={cls} /> {/snippet} {#snippet onIcon(cls)} <BellMinus class={cls} /> {/snippet} {#snippet err(cls)} <TriangleAlert class={cls} /> {/snippet} </ToggleFaces> </Button> </Tooltip> <span role="status" aria-live="polite" class="sr-only">{error ?? ""}</span>{/if}