Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138<script lang="ts"> import { resolve } from "$app/paths"; import Button from "$lib/components/ui/Button.svelte"; import ToggleFaces from "$lib/components/ui/ToggleFaces.svelte"; import Tooltip from "$lib/components/ui/Tooltip.svelte"; import UserRoundPlus from "$icon/user-round-plus"; import UserRoundMinus from "$icon/user-round-minus"; import TriangleAlert from "$icon/triangle-alert"; import { page } from "$app/state"; import { getAuth } from "$lib/auth.svelte"; import { createFollow, deleteFollow } from "$lib/api/graph"; import { createBobbinClient } from "$lib/api/client"; import { awaitDeletedRecord, awaitIndexedOrSaved } from "$lib/api/awaitRecord"; import { getProfileCounts } from "$lib/components/profile/counts.svelte"; import type { FollowChange } from "$lib/components/profile/types"; import { createOptimisticRelation } from "$lib/optimistic.svelte";
interface Props { profileDid: string; initialRkey?: string | null; onCommit?: (change: FollowChange) => void; }
let { profileDid, initialRkey, onCommit }: Props = $props();
const auth = getAuth(); const profileCounts = getProfileCounts(); const signedIn = $derived(Boolean(auth.currentDid)); const isSelf = $derived(auth.currentDid === profileDid); const relation = createOptimisticRelation({ key: () => `${auth.currentDid ?? ""}:${profileDid}`, loadedRkey: () => initialRkey });
const following = $derived(relation.active); const failed = $derived(relation.failed); $effect(() => { if (!failed) return; const timer = setTimeout(() => { relation.resetFailure(); }, 2000); return () => clearTimeout(timer); });
const commit = (change: FollowChange) => { profileCounts?.adjust(change.subjectDid, "followers", change.delta); profileCounts?.adjust(change.viewerDid, "following", change.delta); onCommit?.(change); };
const toggle = async () => { const agent = auth.agent; if (!agent || !relation.known || relation.loading) return; relation.begin(); const bobbinUrl = page.data.publicConfig?.bobbinUrl; const ctx = bobbinUrl ? createBobbinClient({ serviceUrl: bobbinUrl }) : null; try { if (relation.active && relation.rkey) { const { uri } = await deleteFollow(agent, relation.rkey); relation.deleted(); if (ctx) { awaitDeletedRecord(ctx, uri).catch((cause) => console.error("failed to wait for unfollow index", cause) ); } commit({ viewerDid: agent.sub, subjectDid: profileDid, following: false, rkey: null, delta: -1 }); } else { const written = await createFollow(agent, profileDid); relation.created(written.rkey); if (ctx) { // todo: needs a design pass for surfacing index wait failures awaitIndexedOrSaved(ctx, written).catch((cause) => console.error("failed to wait for follow index", cause) ); } commit({ viewerDid: agent.sub, subjectDid: profileDid, following: true, rkey: written.rkey, delta: 1 }); } } catch { relation.fail(); } };</script>
{#if !isSelf} {#if !signedIn} <Button href={resolve("/login")} variant="default" class="w-full gap-xs" insetShadow={true} icon={UserRoundPlus} > Follow </Button> {:else} <Tooltip content="Something went wrong. Try again." disabled={!failed}> <Button variant={failed ? "danger" : "default"} class="w-full min-w-28 gap-xs" insetShadow={true} disabled={!relation.known || relation.loading} aria-busy={relation.loading} onclick={toggle} > <ToggleFaces on={following} {failed} size="regular" offLabel="Follow" onLabel="Unfollow" > {#snippet off(cls)} <UserRoundPlus class={cls} /> {/snippet} {#snippet onIcon(cls)} <UserRoundMinus class={cls} /> {/snippet} {#snippet err(cls)} <TriangleAlert class={cls} /> {/snippet} </ToggleFaces> </Button> </Tooltip> {/if}{/if}