~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
95 lines · 2.9 KB Raw History
<script lang="ts">
	import type { ReactionCountView } from '#lib/lexicons/dev/knotless/defs.ts';
	import type * as addReaction from '#lib/lexicons/dev/knotless/addReaction.ts';
	import type * as removeReaction from '#lib/lexicons/dev/knotless/removeReaction.ts';
	import type { AtUriString } from '@atproto/lex';
	import { call } from '#lib/data.ts';
	import { LOGIN } from '#lib/login.ts';
	import { REACTIONS } from '#lib/reactions.ts';
	import Icon from '#lib/atoms/Icon.svelte';
	import ReactionPill from '#lib/atoms/ReactionPill.svelte';
	/**
	 * How accounts reacted to `subject`, a pull, an issue or a comment on the
	 * repository `repo`, and reacting to it as whoever is `in`, from the
	 * eight a reaction may say: for anyone else, the way to log in first.
	 */
	let {
		repo,
		subject,
		reactions = [],
		in: logged
	}: { repo: string; subject: string; reactions?: ReactionCountView[]; in: boolean } = $props();
	let counts = $derived(reactions);
	// The reaction being added or taken back.
	let busy = $state('');
	let open = $state(false);
	let failed = $state('');
	const mine = (content: string) => !!counts.find((c) => c.content === content)?.reaction;
	const shown = $derived(
		REACTIONS.flatMap((r) => {
			const c = counts.find((c) => c.content === r.content);
			return c && c.count > 0 ? [{ ...r, count: c.count, mine: !!c.reaction }] : [];
		})
	);
	async function toggle(content: string) {
		open = false;
		busy = content;
		const nsid = mine(content) ? 'dev.knotless.removeReaction' : 'dev.knotless.addReaction';
		const input = { repo: repo as AtUriString, subject: subject as AtUriString, content };
		try {
			counts = (await call<typeof addReaction.main | typeof removeReaction.main>(nsid, input))
				.reactions;
			failed = '';
		} catch (e) {
			failed = (e as Error).message;
		}
		busy = '';
	}
</script>

<div class="reactions">
	{#each shown as r (r.content)}
		<ReactionPill
			emoji={r.emoji}
			name={r.name}
			count={r.count}
			mine={r.mine}
			disabled={!!busy}
			busy={busy === r.content}
			onclick={logged ? () => toggle(r.content) : undefined}
			target={logged ? undefined : LOGIN}
		/>
	{/each}
	{#if logged}
		<details class="popover reaction-menu" bind:open>
			<summary
				class="reaction add"
				aria-label="Add reaction"
				title="Add reaction"
				aria-busy={!!busy && !shown.some((r) => r.content === busy)}><Icon name="smiley" /></summary
			>
			<div class="menu reaction-picker">
				{#each REACTIONS as r (r.content)}
					<button
						class="reaction-pick"
						type="button"
						aria-label={r.name}
						title={r.name}
						aria-pressed={mine(r.content)}
						disabled={!!busy}
						onclick={() => toggle(r.content)}>{r.emoji}</button
					>
				{/each}
			</div>
		</details>
	{:else}
		<button
			class="reaction add"
			type="button"
			aria-label="Add reaction"
			title="Add reaction"
			popovertarget={LOGIN}><Icon name="smiley" /></button
		>
	{/if}
	{#if failed}<span class="reaction-error">{failed}</span>{/if}
</div>