Coves frontend - a photon fork
at main 128 lines 3.4 kB view raw
1<script lang="ts"> 2 import { profile } from '$lib/app/auth.svelte' 3 import { errorMessage } from '$lib/app/error' 4 import { t } from '$lib/app/i18n' 5 import { uploadImage } from '$lib/app/util.svelte' 6 import { Button, toast } from 'mono-svelte' 7 import { DocumentPlus, Icon } from 'svelte-hero-icons/dist' 8 import { expoOut } from 'svelte/easing' 9 import { slide } from 'svelte/transition' 10 import ProgressBar from '../info/ProgressBar.svelte' 11 12 interface Props { 13 image?: FileList | null 14 preview?: boolean 15 multiple?: boolean 16 onupload?: (urls: string[]) => void 17 } 18 19 let { 20 image = $bindable(null), 21 preview = true, 22 multiple = true, 23 onupload, 24 }: Props = $props() 25 let progress = $state(1) 26 27 let previewURLs = $derived( 28 preview && image ? Array.from(image).map(URL.createObjectURL) : undefined, 29 ) 30 31 async function upload() { 32 if (!profile.current?.jwt || image == null) return 33 34 progress = 0 35 36 try { 37 const uploaded = ( 38 await Promise.all( 39 Array.from(image).map((i) => 40 uploadImage(i, profile.current.instance, profile.current.jwt!) 41 .then((uploaded) => { 42 progress += 1 / (image?.length ?? 0) 43 return uploaded 44 }) 45 .catch((err) => { 46 toast({ content: errorMessage(err), type: 'error' }) 47 return 'Failed to upload' 48 }), 49 ), 50 ) 51 ).filter((i) => i != undefined) 52 53 if (!uploaded) throw new Error('Image upload returned undefined') 54 55 onupload?.(uploaded) 56 progress = 1 57 } catch (err) { 58 toast({ 59 content: errorMessage(err), 60 type: 'error', 61 }) 62 } 63 64 progress = 1 65 } 66</script> 67 68{#if progress != 1} 69 <div transition:slide={{ duration: 500, easing: expoOut }} class="w-full"> 70 <ProgressBar {progress} /> 71 </div> 72{/if} 73<form 74 class="flex flex-col gap-4" 75 onsubmit={(e) => { 76 e.preventDefault() 77 upload() 78 }} 79> 80 <label 81 for="image-input" 82 class="p-4 border-2 border-dashed rounded-xl cursor-pointer transition-colors 83 border-slate-300 dark:border-zinc-700 hover:border-primary-900 dark:hover:border-primary-100" 84 > 85 {#if previewURLs} 86 <div 87 class="grid gap-x-2 gap-y-px max-h-64 overflow-auto 88 {multiple ? 'grid-cols-3 grid-rows-2' : 'grid-cols-1 grid-rows-1'}" 89 > 90 {#each previewURLs as file} 91 <img 92 src={file} 93 onload={() => URL.revokeObjectURL(file)} 94 alt={file} 95 class="w-full rounded-md h-full object-cover transition-all 96 border border-slate-200 dark:border-zinc-800 ring-1 97 ring-slate-50 dark:ring-zinc-950 bg-white dark:bg-zinc-950" 98 /> 99 {/each} 100 </div> 101 {:else} 102 <div 103 class="flex flex-col justify-center w-full items-center gap-2 104 text-slate-600 dark:text-zinc-400" 105 > 106 <Icon src={DocumentPlus} size="32" /> 107 <span class="font-medium text-sm">{$t('form.post.selectFile')}</span> 108 </div> 109 {/if} 110 <input 111 {multiple} 112 type="file" 113 accept="image/*" 114 class="hidden" 115 id="image-input" 116 bind:files={image} 117 /> 118 </label> 119 <Button 120 loading={progress != 1} 121 disabled={progress != 1 || image == null} 122 submit 123 color="primary" 124 size="lg" 125 > 126 Upload 127 </Button> 128</form>