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