your personal website on atproto - mirror blento.app
at update-docs 130 lines 4.1 kB view raw
1<script lang="ts"> 2 import { onMount } from 'svelte'; 3 import type { ContentComponentProps } from '../../types'; 4 import { getAdditionalUserData, getCanEdit, getIsMobile } from '$lib/website/context'; 5 import { getBlentoOrBskyProfile } from '$lib/atproto/methods'; 6 import type { FriendsProfile } from '.'; 7 import type { Did } from '@atcute/lexicons'; 8 import { Avatar } from '@foxui/core'; 9 10 let { item }: ContentComponentProps = $props(); 11 12 const isMobile = getIsMobile(); 13 const canEdit = getCanEdit(); 14 const additionalData = getAdditionalUserData(); 15 16 let dids: string[] = $derived(item.cardData.friends ?? []); 17 18 let serverProfiles: FriendsProfile[] = $derived( 19 (additionalData[item.cardType] as FriendsProfile[]) ?? [] 20 ); 21 22 let clientProfiles: FriendsProfile[] = $state([]); 23 24 let profiles = $derived.by(() => { 25 if (serverProfiles.length > 0) { 26 return dids 27 .map((did) => serverProfiles.find((p) => p.did === did)) 28 .filter((p): p is FriendsProfile => !!p); 29 } 30 return dids 31 .map((did) => clientProfiles.find((p) => p.did === did)) 32 .filter((p): p is FriendsProfile => !!p); 33 }); 34 35 onMount(() => { 36 if (serverProfiles.length === 0 && dids.length > 0) { 37 loadProfiles(); 38 } 39 }); 40 41 async function loadProfiles() { 42 const results = await Promise.all( 43 dids.map((did) => getBlentoOrBskyProfile({ did: did as Did }).catch(() => undefined)) 44 ); 45 clientProfiles = results.filter( 46 (p): p is FriendsProfile => !!p && p.handle !== 'handle.invalid' 47 ); 48 } 49 50 // Reload when dids change in editing mode 51 $effect(() => { 52 if (canEdit() && dids.length > 0) { 53 loadProfiles(); 54 } 55 }); 56 57 let sizeClass = $derived.by(() => { 58 const w = isMobile() ? item.mobileW / 2 : item.w; 59 if (w < 3) return 'sm'; 60 if (w < 5) return 'md'; 61 return 'lg'; 62 }); 63 64 function removeFriend(did: string) { 65 item.cardData.friends = item.cardData.friends.filter((d: string) => d !== did); 66 } 67 68 function getLink(profile: FriendsProfile): string { 69 if (profile.hasBlento && profile.handle && profile.handle !== 'handle.invalid') { 70 return `/${profile.handle}`; 71 } 72 if (profile.handle && profile.handle !== 'handle.invalid') { 73 return `https://bsky.app/profile/${profile.handle}`; 74 } 75 return `https://bsky.app/profile/${profile.did}`; 76 } 77</script> 78 79<div class="flex h-full w-full items-center justify-center overflow-hidden px-2"> 80 {#if dids.length === 0} 81 {#if canEdit()} 82 <span class="text-base-400 dark:text-base-500 accent:text-accent-300 text-sm"> 83 Add friends in settings 84 </span> 85 {/if} 86 {:else} 87 {@const olX = sizeClass === 'sm' ? 12 : sizeClass === 'md' ? 20 : 24} 88 {@const olY = sizeClass === 'sm' ? 8 : sizeClass === 'md' ? 12 : 16} 89 <div class=""> 90 <div class="flex flex-wrap items-center justify-center" style="padding: {olY}px 0 0 {olX}px;"> 91 {#each profiles as profile (profile.did)} 92 <div class="group relative" style="margin: -{olY}px 0 0 -{olX}px;"> 93 <a 94 href={getLink(profile)} 95 class="accent:ring-accent-500 relative block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 96 > 97 <Avatar 98 src={profile.avatar} 99 alt={profile.handle} 100 class={sizeClass === 'sm' ? 'size-12' : sizeClass === 'md' ? 'size-16' : 'size-20'} 101 /> 102 </a> 103 {#if canEdit()} 104 <button 105 aria-label="Remove friend" 106 class="absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black/50 text-white opacity-0 transition-opacity group-hover:opacity-100" 107 onclick={(e) => { 108 e.preventDefault(); 109 e.stopPropagation(); 110 removeFriend(profile.did); 111 }} 112 > 113 <svg 114 xmlns="http://www.w3.org/2000/svg" 115 fill="none" 116 viewBox="0 0 24 24" 117 stroke-width="2.5" 118 stroke="currentColor" 119 class="size-4" 120 > 121 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /> 122 </svg> 123 </button> 124 {/if} 125 </div> 126 {/each} 127 </div> 128 </div> 129 {/if} 130</div>