your personal website on atproto - mirror blento.app
at next 103 lines 3.2 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 getLink(profile: FriendsProfile): string { 65 if (profile.hasBlento && profile.handle && profile.handle !== 'handle.invalid') { 66 return `/${profile.handle}`; 67 } 68 if (profile.handle && profile.handle !== 'handle.invalid') { 69 return `https://bsky.app/profile/${profile.handle}`; 70 } 71 return `https://bsky.app/profile/${profile.did}`; 72 } 73</script> 74 75<div class="flex h-full w-full items-center justify-center overflow-hidden px-2"> 76 {#if dids.length === 0} 77 {#if canEdit()} 78 <span class="text-base-400 dark:text-base-500 accent:text-accent-300 text-sm"> 79 Add friends in settings 80 </span> 81 {/if} 82 {:else} 83 {@const olX = sizeClass === 'sm' ? 12 : sizeClass === 'md' ? 20 : 24} 84 {@const olY = sizeClass === 'sm' ? 8 : sizeClass === 'md' ? 12 : 16} 85 <div class=""> 86 <div class="flex flex-wrap items-center justify-center" style="padding: {olY}px 0 0 {olX}px;"> 87 {#each profiles as profile (profile.did)} 88 <a 89 href={getLink(profile)} 90 class="accent:ring-accent-500 relative block rounded-full ring-2 ring-white transition-transform hover:scale-110 dark:ring-neutral-900" 91 style="margin: -{olY}px 0 0 -{olX}px;" 92 > 93 <Avatar 94 src={profile.avatar} 95 alt={profile.handle} 96 class={sizeClass === 'sm' ? 'size-12' : sizeClass === 'md' ? 'size-16' : 'size-20'} 97 /> 98 </a> 99 {/each} 100 </div> 101 </div> 102 {/if} 103</div>