your personal website on atproto - mirror blento.app
at edit-profile 158 lines 4.7 kB view raw
1<script lang="ts"> 2 import type { WebsiteData } from '$lib/types'; 3 import { getDescription, getName, getImage, compressImage } from '$lib/helper'; 4 import PlainTextEditor from '$lib/components/PlainTextEditor.svelte'; 5 import MarkdownTextEditor from '$lib/components/MarkdownTextEditor.svelte'; 6 import type { Editor } from '@tiptap/core'; 7 8 let { data = $bindable() }: { data: WebsiteData } = $props(); 9 10 let fileInput: HTMLInputElement; 11 let isHoveringAvatar = $state(false); 12 let descriptionEditor: Editor | null = $state(null); 13 14 // Initialize publication if needed 15 $effect(() => { 16 if (!data.publication) { 17 data.publication = { 18 name: getName(data), 19 description: getDescription(data) 20 }; 21 } else { 22 if (data.publication.name === undefined) { 23 data.publication.name = getName(data); 24 } 25 if (data.publication.description === undefined) { 26 data.publication.description = getDescription(data); 27 } 28 } 29 }); 30 31 async function handleAvatarChange(event: Event) { 32 const target = event.target as HTMLInputElement; 33 const file = target.files?.[0]; 34 if (!file) return; 35 36 try { 37 const compressedBlob = await compressImage(file); 38 const objectUrl = URL.createObjectURL(compressedBlob); 39 40 data.publication ??= {}; 41 data.publication.icon = { 42 blob: compressedBlob, 43 objectUrl 44 } as any; 45 46 data = { ...data }; 47 } catch (error) { 48 console.error('Failed to process image:', error); 49 } 50 } 51 52 function getAvatarUrl(): string | undefined { 53 const customIcon = getImage(data.publication ?? {}, data.did, 'icon'); 54 if (customIcon) return customIcon; 55 return data.profile.avatar; 56 } 57 58 function handleFileInputClick() { 59 fileInput.click(); 60 } 61</script> 62 63<div 64 class="mx-auto flex max-w-lg flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12" 65> 66 <div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24"> 67 <!-- Avatar with edit capability --> 68 <button 69 type="button" 70 class="group relative size-32 cursor-pointer overflow-hidden rounded-full @5xl/wrapper:size-44" 71 onmouseenter={() => (isHoveringAvatar = true)} 72 onmouseleave={() => (isHoveringAvatar = false)} 73 onclick={handleFileInputClick} 74 > 75 {#if getAvatarUrl()} 76 <img 77 class="border-base-400 dark:border-base-800 size-full rounded-full border object-cover" 78 src={getAvatarUrl()} 79 alt="" 80 /> 81 {:else} 82 <div class="bg-base-300 dark:bg-base-700 size-full rounded-full"></div> 83 {/if} 84 85 <!-- Hover overlay --> 86 <div 87 class={[ 88 'absolute inset-0 flex items-center justify-center rounded-full bg-black/50 transition-opacity duration-200', 89 isHoveringAvatar ? 'opacity-100' : 'opacity-0' 90 ]} 91 > 92 <div class="text-center text-sm text-white"> 93 <svg 94 xmlns="http://www.w3.org/2000/svg" 95 fill="none" 96 viewBox="0 0 24 24" 97 stroke-width="1.5" 98 stroke="currentColor" 99 class="mx-auto mb-1 size-6" 100 > 101 <path 102 stroke-linecap="round" 103 stroke-linejoin="round" 104 d="M6.827 6.175A2.31 2.31 0 0 1 5.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 0 0-1.134-.175 2.31 2.31 0 0 1-1.64-1.055l-.822-1.316a2.192 2.192 0 0 0-1.736-1.039 48.774 48.774 0 0 0-5.232 0 2.192 2.192 0 0 0-1.736 1.039l-.821 1.316Z" 105 /> 106 <path 107 stroke-linecap="round" 108 stroke-linejoin="round" 109 d="M16.5 12.75a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0ZM18.75 10.5h.008v.008h-.008V10.5Z" 110 /> 111 </svg> 112 <span>Click to change</span> 113 </div> 114 </div> 115 </button> 116 117 <input 118 bind:this={fileInput} 119 type="file" 120 accept="image/*" 121 class="hidden" 122 onchange={handleAvatarChange} 123 /> 124 125 <!-- Editable Name --> 126 {#if data.publication} 127 <div class="text-4xl font-bold wrap-anywhere"> 128 <PlainTextEditor bind:contentDict={data.publication} key="name" placeholder="Your name" /> 129 </div> 130 {/if} 131 132 <!-- Editable Description --> 133 <div class="scrollbar -mx-4 grow overflow-x-hidden overflow-y-scroll px-4"> 134 {#if data.publication} 135 136 137 <MarkdownTextEditor 138 bind:editor={descriptionEditor} 139 bind:contentDict={data.publication} 140 key="description" 141 placeholder="Add a description... (supports markdown)" 142 class="" 143 /> 144 {/if} 145 </div> 146 147 <div class="h-10.5 w-1 @5xl/wrapper:hidden"></div> 148 149 <div class="hidden text-xs font-light @5xl/wrapper:block"> 150 made with <a 151 href="https://blento.app" 152 target="_blank" 153 class="hover:text-accent-600 dark:hover:text-accent-400 font-medium transition-colors duration-200" 154 >blento</a 155 > 156 </div> 157 </div> 158</div>