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