This repository has no description
1<script lang="ts">
2 import Star from "$icon/star";
3 import { getAuth } from "$lib/auth.svelte";
4 import { createStar, deleteStar } from "$lib/api/graph";
5 import Button from "$lib/components/ui/Button.svelte";
6 import ButtonGroup from "$lib/components/ui/ButtonGroup.svelte";
7 import { getProfileCounts } from "$lib/components/profile/counts.svelte";
8 import { createOptimisticRelation, createOptimisticCount } from "$lib/optimistic.svelte";
9
10 interface Props {
11 repoDid: string;
12 repoOwnerHandle: string;
13 repoName: string;
14 initialCount?: number;
15 initialRkey?: string | null;
16 }
17
18 let { repoDid, repoOwnerHandle, repoName, initialCount, initialRkey }: Props = $props();
19
20 const auth = getAuth();
21 const profileCounts = getProfileCounts();
22 const signedIn = $derived(Boolean(auth.currentDid));
23 const relation = createOptimisticRelation({
24 key: () => `${auth.currentDid ?? ""}:${repoDid}`,
25 loadedRkey: () => initialRkey
26 });
27 const starCount = createOptimisticCount({
28 key: () => repoDid,
29 loaded: () => initialCount
30 });
31
32 let busy = $state(false);
33 const starred = $derived(relation.active);
34 const failed = $derived(relation.failed || starCount.failed);
35
36 const toggle = async () => {
37 const agent = auth.agent;
38 if (!agent || busy || !relation.known || !repoDid) return;
39 busy = true;
40 relation.resetFailure();
41 starCount.resetFailure();
42 try {
43 if (relation.active && relation.rkey) {
44 await deleteStar(agent, relation.rkey);
45 relation.deleted();
46 starCount.adjust(-1);
47 profileCounts?.adjust(agent.sub, "stars", -1);
48 } else {
49 const rkey = await createStar(agent, repoDid);
50 relation.created(rkey);
51 starCount.adjust(1);
52 profileCounts?.adjust(agent.sub, "stars", 1);
53 }
54 } catch {
55 relation.fail();
56 starCount.fail();
57 } finally {
58 busy = false;
59 }
60 };
61</script>
62
63{#if signedIn && repoDid}
64 <div class="flex flex-col items-end">
65 <ButtonGroup>
66 <Button
67 variant="default"
68 size="sm"
69 insetShadow={true}
70 class="flex-1"
71 loading={busy}
72 disabled={!relation.known}
73 onclick={toggle}
74 >
75 <Star
76 class={`size-4 shrink-0 ${starred ? "[&_path]:fill-[currentColor]" : ""}`}
77 aria-hidden="true"
78 />
79 <span>{starred ? "Unstar" : "Star"}</span>
80 </Button>
81 <Button
82 href={`/${repoOwnerHandle}/${repoName}/stars`}
83 variant="ghost"
84 size="sm"
85 title="Starred by"
86 >
87 {starCount.value}
88 </Button>
89 </ButtonGroup>
90 {#if failed}
91 <p class="mt-1 typography-paragraph-small text-foreground-danger">
92 Something went wrong. Try again.
93 </p>
94 {/if}
95 </div>
96{/if}