your personal website on atproto - mirror blento.app
at improve-oauth 283 lines 7.4 kB view raw
1<script lang="ts"> 2 import { dev } from '$app/environment'; 3 import { user } from '$lib/atproto'; 4 import type { WebsiteData } from '$lib/types'; 5 import { Button, Input, Navbar, Popover, Toggle, toast } from '@foxui/core'; 6 7 let { 8 data, 9 linkValue = $bindable(), 10 newCard, 11 addLink, 12 13 showingMobileView = $bindable(), 14 isSaving = $bindable(), 15 hasUnsavedChanges, 16 17 save, 18 19 handleImageInputChange, 20 handleVideoInputChange 21 }: { 22 data: WebsiteData; 23 linkValue: string; 24 newCard: (type: string) => void; 25 addLink: (url: string) => void; 26 27 showingMobileView: boolean; 28 29 isSaving: boolean; 30 hasUnsavedChanges: boolean; 31 32 save: () => Promise<void>; 33 34 handleImageInputChange: (evt: Event) => void; 35 handleVideoInputChange: (evt: Event) => void; 36 } = $props(); 37 38 let linkPopoverOpen = $state(false); 39 40 let imageInputRef: HTMLInputElement | undefined = $state(); 41 let videoInputRef: HTMLInputElement | undefined = $state(); 42 43 function getShareUrl() { 44 const base = typeof window !== 'undefined' ? window.location.origin : ''; 45 const pagePath = 46 data.page && data.page !== 'blento.self' ? `/${data.page.replace('blento.', '')}` : ''; 47 return `${base}/${data.handle}${pagePath}`; 48 } 49 50 async function copyShareLink() { 51 const url = getShareUrl(); 52 await navigator.clipboard.writeText(url); 53 toast.success('Link copied to clipboard!'); 54 } 55</script> 56 57<input 58 type="file" 59 accept="image/*" 60 onchange={handleImageInputChange} 61 class="hidden" 62 multiple 63 bind:this={imageInputRef} 64/> 65 66<input 67 type="file" 68 accept="video/*" 69 onchange={handleVideoInputChange} 70 class="hidden" 71 multiple 72 bind:this={videoInputRef} 73/> 74 75{#if dev || (user.isLoggedIn && user.profile?.did === data.did)} 76 <Navbar 77 class={[ 78 'dark:bg-base-900 bg-base-100 top-auto bottom-2 mx-4 mt-3 max-w-3xl rounded-full px-4 md:mx-auto lg:inline-flex', 79 !dev ? 'hidden' : '' 80 ]} 81 > 82 <div class="flex items-center gap-2"> 83 <Button 84 size="iconLg" 85 variant="ghost" 86 class="backdrop-blur-none" 87 onclick={() => { 88 newCard('section'); 89 }} 90 > 91 <svg 92 xmlns="http://www.w3.org/2000/svg" 93 viewBox="0 0 24 24" 94 fill="none" 95 stroke="currentColor" 96 stroke-width="2" 97 stroke-linecap="round" 98 stroke-linejoin="round" 99 ><path d="M6 12h12" /><path d="M6 20V4" /><path d="M18 20V4" /></svg 100 > 101 </Button> 102 103 <Button 104 size="iconLg" 105 variant="ghost" 106 class="backdrop-blur-none" 107 onclick={() => { 108 newCard('text'); 109 }} 110 > 111 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" 112 ><path 113 fill="none" 114 stroke="currentColor" 115 stroke-linecap="round" 116 stroke-linejoin="round" 117 stroke-width="2" 118 d="m15 16l2.536-7.328a1.02 1.02 1 0 1 1.928 0L22 16m-6.303-2h5.606M2 16l4.039-9.69a.5.5 0 0 1 .923 0L11 16m-7.696-3h6.392" 119 /></svg 120 > 121 </Button> 122 123 <Popover sideOffset={16} bind:open={linkPopoverOpen} class="bg-base-100 dark:bg-base-900"> 124 {#snippet child({ props })} 125 <Button 126 size="iconLg" 127 variant="ghost" 128 class="backdrop-blur-none" 129 onclick={() => { 130 newCard('link'); 131 }} 132 {...props} 133 > 134 <svg 135 xmlns="http://www.w3.org/2000/svg" 136 fill="none" 137 viewBox="-2 -2 28 28" 138 stroke-width="2" 139 stroke="currentColor" 140 > 141 <path 142 stroke-linecap="round" 143 stroke-linejoin="round" 144 d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" 145 /> 146 </svg> 147 </Button> 148 {/snippet} 149 <Input 150 spellcheck={false} 151 type="url" 152 bind:value={linkValue} 153 onkeydown={(event) => { 154 if (event.code === 'Enter') { 155 addLink(linkValue); 156 event.preventDefault(); 157 } 158 }} 159 placeholder="Enter link" 160 /> 161 <Button onclick={() => addLink(linkValue)} size="icon" 162 ><svg 163 xmlns="http://www.w3.org/2000/svg" 164 fill="none" 165 viewBox="0 0 24 24" 166 stroke-width="2" 167 stroke="currentColor" 168 class="size-6" 169 > 170 <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /> 171 </svg> 172 </Button> 173 </Popover> 174 175 <Button 176 size="iconLg" 177 variant="ghost" 178 class="backdrop-blur-none" 179 onclick={() => { 180 imageInputRef?.click(); 181 }} 182 > 183 <svg 184 xmlns="http://www.w3.org/2000/svg" 185 fill="none" 186 viewBox="0 0 24 24" 187 stroke-width="2" 188 stroke="currentColor" 189 > 190 <path 191 stroke-linecap="round" 192 stroke-linejoin="round" 193 d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" 194 /> 195 </svg> 196 </Button> 197 198 {#if dev} 199 <Button 200 size="iconLg" 201 variant="ghost" 202 class="backdrop-blur-none" 203 onclick={() => { 204 videoInputRef?.click(); 205 }} 206 > 207 <svg 208 xmlns="http://www.w3.org/2000/svg" 209 fill="none" 210 viewBox="0 0 24 24" 211 stroke-width="1.5" 212 stroke="currentColor" 213 > 214 <path 215 stroke-linecap="round" 216 stroke-linejoin="round" 217 d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" 218 /> 219 </svg> 220 </Button> 221 {/if} 222 223 <Button size="iconLg" variant="ghost" class="backdrop-blur-none" popovertarget="mobile-menu"> 224 <svg 225 xmlns="http://www.w3.org/2000/svg" 226 fill="none" 227 viewBox="0 0 24 24" 228 stroke-width="1.5" 229 stroke="currentColor" 230 > 231 <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 232 </svg> 233 </Button> 234 </div> 235 <div class="flex items-center gap-2"> 236 <Toggle 237 class="hidden bg-transparent backdrop-blur-none lg:block dark:bg-transparent" 238 bind:pressed={showingMobileView} 239 > 240 <svg 241 xmlns="http://www.w3.org/2000/svg" 242 fill="none" 243 viewBox="0 0 24 24" 244 stroke-width="1.5" 245 stroke="currentColor" 246 class="size-6" 247 > 248 <path 249 stroke-linecap="round" 250 stroke-linejoin="round" 251 d="M10.5 1.5H8.25A2.25 2.25 0 0 0 6 3.75v16.5a2.25 2.25 0 0 0 2.25 2.25h7.5A2.25 2.25 0 0 0 18 20.25V3.75a2.25 2.25 0 0 0-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" 252 /> 253 </svg> 254 </Toggle> 255 {#if hasUnsavedChanges} 256 <Button 257 disabled={isSaving} 258 onclick={async () => { 259 save(); 260 }}>{isSaving ? 'Saving...' : 'Save'}</Button 261 > 262 {:else} 263 <Button onclick={copyShareLink}> 264 <svg 265 xmlns="http://www.w3.org/2000/svg" 266 fill="none" 267 viewBox="0 0 24 24" 268 stroke-width="1.5" 269 stroke="currentColor" 270 class="size-5" 271 > 272 <path 273 stroke-linecap="round" 274 stroke-linejoin="round" 275 d="M7.217 10.907a2.25 2.25 0 100 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186l9.566-5.314m-9.566 7.5l9.566 5.314m0 0a2.25 2.25 0 103.935 2.186 2.25 2.25 0 00-3.935-2.186zm0-12.814a2.25 2.25 0 103.933-2.185 2.25 2.25 0 00-3.933 2.185z" 276 /> 277 </svg> 278 Share 279 </Button> 280 {/if} 281 </div> 282 </Navbar> 283{/if}