your personal website on atproto - mirror blento.app
at next 94 lines 2.9 kB view raw
1<script lang="ts"> 2 import { onMount } from 'svelte'; 3 import type { Item } from '$lib/types'; 4 import type { SettingsComponentProps } from '../types'; 5 import type { AppBskyActorDefs } from '@atcute/bluesky'; 6 import type { Did } from '@atcute/lexicons'; 7 import type { FriendsProfile } from '.'; 8 import { getBlentoOrBskyProfile } from '$lib/atproto/methods'; 9 import HandleInput from '$lib/atproto/UI/HandleInput.svelte'; 10 import { Avatar, Button } from '@foxui/core'; 11 12 let { item = $bindable<Item>() }: SettingsComponentProps = $props(); 13 14 let handleValue = $state(''); 15 let inputRef: HTMLInputElement | null = $state(null); 16 let profiles: FriendsProfile[] = $state([]); 17 18 let dids: string[] = $derived(item.cardData.friends ?? []); 19 20 onMount(() => { 21 loadProfiles(); 22 }); 23 24 async function loadProfiles() { 25 const results = await Promise.all( 26 dids.map((did) => getBlentoOrBskyProfile({ did: did as Did }).catch(() => undefined)) 27 ); 28 profiles = results.filter((p): p is FriendsProfile => !!p && p.handle !== 'handle.invalid'); 29 } 30 31 function addFriend(actor: AppBskyActorDefs.ProfileViewBasic) { 32 if (!item.cardData.friends) item.cardData.friends = []; 33 if (item.cardData.friends.includes(actor.did)) return; 34 item.cardData.friends = [...item.cardData.friends, actor.did]; 35 profiles = [ 36 ...profiles, 37 { 38 did: actor.did, 39 handle: actor.handle, 40 displayName: actor.displayName || actor.handle, 41 avatar: actor.avatar, 42 hasBlento: false 43 } as FriendsProfile 44 ]; 45 requestAnimationFrame(() => { 46 handleValue = ''; 47 if (inputRef) inputRef.value = ''; 48 }); 49 } 50 51 function removeFriend(did: string) { 52 item.cardData.friends = item.cardData.friends.filter((d: string) => d !== did); 53 profiles = profiles.filter((p) => p.did !== did); 54 } 55 56 function getProfile(did: string): FriendsProfile | undefined { 57 return profiles.find((p) => p.did === did); 58 } 59</script> 60 61<div class="flex flex-col gap-3"> 62 <HandleInput bind:value={handleValue} onselected={addFriend} bind:ref={inputRef} /> 63 64 {#if dids.length > 0} 65 <div class="flex flex-col gap-1.5"> 66 {#each dids as did (did)} 67 {@const profile = getProfile(did)} 68 <div class="flex items-center gap-2"> 69 <Avatar src={profile?.avatar} alt={profile?.handle ?? did} class="size-6 rounded-full" /> 70 <span class="min-w-0 flex-1 truncate text-sm"> 71 {profile?.handle ?? did} 72 </span> 73 <Button 74 variant="ghost" 75 size="icon" 76 class="size-6 min-w-6" 77 onclick={() => removeFriend(did)} 78 > 79 <svg 80 xmlns="http://www.w3.org/2000/svg" 81 fill="none" 82 viewBox="0 0 24 24" 83 stroke-width="2" 84 stroke="currentColor" 85 class="size-3.5" 86 > 87 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /> 88 </svg> 89 </Button> 90 </div> 91 {/each} 92 </div> 93 {/if} 94</div>