your personal website on atproto - mirror blento.app
at card-command-bar 288 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 showCardCommand 23 }: { 24 data: WebsiteData; 25 linkValue: string; 26 newCard: (type: string) => void; 27 addLink: (url: string) => void; 28 29 showingMobileView: boolean; 30 31 isSaving: boolean; 32 hasUnsavedChanges: boolean; 33 34 save: () => Promise<void>; 35 36 handleImageInputChange: (evt: Event) => void; 37 handleVideoInputChange: (evt: Event) => void; 38 39 showCardCommand: () => void; 40 } = $props(); 41 42 let linkPopoverOpen = $state(false); 43 44 let imageInputRef: HTMLInputElement | undefined = $state(); 45 let videoInputRef: HTMLInputElement | undefined = $state(); 46 47 function getShareUrl() { 48 const base = typeof window !== 'undefined' ? window.location.origin : ''; 49 const pagePath = 50 data.page && data.page !== 'blento.self' ? `/${data.page.replace('blento.', '')}` : ''; 51 return `${base}/${data.handle}${pagePath}`; 52 } 53 54 async function copyShareLink() { 55 const url = getShareUrl(); 56 await navigator.clipboard.writeText(url); 57 toast.success('Link copied to clipboard!'); 58 } 59</script> 60 61<input 62 type="file" 63 accept="image/*" 64 onchange={handleImageInputChange} 65 class="hidden" 66 id="image-input" 67 multiple 68 bind:this={imageInputRef} 69/> 70 71<input 72 type="file" 73 accept="video/*" 74 onchange={handleVideoInputChange} 75 class="hidden" 76 multiple 77 bind:this={videoInputRef} 78/> 79 80{#if dev || (user.isLoggedIn && user.profile?.did === data.did)} 81 <Navbar 82 class={[ 83 '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', 84 !dev ? 'hidden' : '' 85 ]} 86 > 87 <div class="flex items-center gap-2"> 88 <Button 89 size="iconLg" 90 variant="ghost" 91 class="backdrop-blur-none" 92 onclick={() => { 93 newCard('section'); 94 }} 95 > 96 <svg 97 xmlns="http://www.w3.org/2000/svg" 98 viewBox="0 0 24 24" 99 fill="none" 100 stroke="currentColor" 101 stroke-width="2" 102 stroke-linecap="round" 103 stroke-linejoin="round" 104 ><path d="M6 12h12" /><path d="M6 20V4" /><path d="M18 20V4" /></svg 105 > 106 </Button> 107 108 <Button 109 size="iconLg" 110 variant="ghost" 111 class="backdrop-blur-none" 112 onclick={() => { 113 newCard('text'); 114 }} 115 > 116 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" 117 ><path 118 fill="none" 119 stroke="currentColor" 120 stroke-linecap="round" 121 stroke-linejoin="round" 122 stroke-width="2" 123 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" 124 /></svg 125 > 126 </Button> 127 128 <Popover sideOffset={16} bind:open={linkPopoverOpen} class="bg-base-100 dark:bg-base-900"> 129 {#snippet child({ props })} 130 <Button 131 size="iconLg" 132 variant="ghost" 133 class="backdrop-blur-none" 134 onclick={() => { 135 newCard('link'); 136 }} 137 {...props} 138 > 139 <svg 140 xmlns="http://www.w3.org/2000/svg" 141 fill="none" 142 viewBox="-2 -2 28 28" 143 stroke-width="2" 144 stroke="currentColor" 145 > 146 <path 147 stroke-linecap="round" 148 stroke-linejoin="round" 149 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" 150 /> 151 </svg> 152 </Button> 153 {/snippet} 154 <Input 155 spellcheck={false} 156 type="url" 157 bind:value={linkValue} 158 onkeydown={(event) => { 159 if (event.code === 'Enter') { 160 addLink(linkValue); 161 event.preventDefault(); 162 } 163 }} 164 placeholder="Enter link" 165 /> 166 <Button onclick={() => addLink(linkValue)} size="icon" 167 ><svg 168 xmlns="http://www.w3.org/2000/svg" 169 fill="none" 170 viewBox="0 0 24 24" 171 stroke-width="2" 172 stroke="currentColor" 173 class="size-6" 174 > 175 <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" /> 176 </svg> 177 </Button> 178 </Popover> 179 180 <Button 181 size="iconLg" 182 variant="ghost" 183 class="backdrop-blur-none" 184 onclick={() => { 185 imageInputRef?.click(); 186 }} 187 > 188 <svg 189 xmlns="http://www.w3.org/2000/svg" 190 fill="none" 191 viewBox="0 0 24 24" 192 stroke-width="2" 193 stroke="currentColor" 194 > 195 <path 196 stroke-linecap="round" 197 stroke-linejoin="round" 198 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" 199 /> 200 </svg> 201 </Button> 202 203 {#if dev} 204 <Button 205 size="iconLg" 206 variant="ghost" 207 class="backdrop-blur-none" 208 onclick={() => { 209 videoInputRef?.click(); 210 }} 211 > 212 <svg 213 xmlns="http://www.w3.org/2000/svg" 214 fill="none" 215 viewBox="0 0 24 24" 216 stroke-width="1.5" 217 stroke="currentColor" 218 > 219 <path 220 stroke-linecap="round" 221 stroke-linejoin="round" 222 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" 223 /> 224 </svg> 225 </Button> 226 {/if} 227 228 <Button size="iconLg" variant="ghost" class="backdrop-blur-none" onclick={showCardCommand}> 229 <svg 230 xmlns="http://www.w3.org/2000/svg" 231 fill="none" 232 viewBox="0 0 24 24" 233 stroke-width="1.5" 234 stroke="currentColor" 235 > 236 <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 237 </svg> 238 </Button> 239 </div> 240 <div class="flex items-center gap-2"> 241 <Toggle 242 class="hidden bg-transparent backdrop-blur-none lg:block dark:bg-transparent" 243 bind:pressed={showingMobileView} 244 > 245 <svg 246 xmlns="http://www.w3.org/2000/svg" 247 fill="none" 248 viewBox="0 0 24 24" 249 stroke-width="1.5" 250 stroke="currentColor" 251 class="size-6" 252 > 253 <path 254 stroke-linecap="round" 255 stroke-linejoin="round" 256 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" 257 /> 258 </svg> 259 </Toggle> 260 {#if hasUnsavedChanges} 261 <Button 262 disabled={isSaving} 263 onclick={async () => { 264 save(); 265 }}>{isSaving ? 'Saving...' : 'Save'}</Button 266 > 267 {:else} 268 <Button onclick={copyShareLink}> 269 <svg 270 xmlns="http://www.w3.org/2000/svg" 271 fill="none" 272 viewBox="0 0 24 24" 273 stroke-width="1.5" 274 stroke="currentColor" 275 class="size-5" 276 > 277 <path 278 stroke-linecap="round" 279 stroke-linejoin="round" 280 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" 281 /> 282 </svg> 283 Share 284 </Button> 285 {/if} 286 </div> 287 </Navbar> 288{/if}