flotilla/src/app/components/InputProfilePicture.svelte

106 lines
3 KiB
Svelte
Raw Normal View History

2024-08-08 23:32:46 +00:00
<script lang="ts">
import {randomId, call} from "@welshman/lib"
2025-09-18 19:17:31 +00:00
import {preventDefault, stopPropagation, compressFile} from "@lib/html"
2025-09-10 22:26:39 +00:00
import CloseCircle from "@assets/icons/close-circle.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import GallerySend from "@assets/icons/gallery-send.svg?dataurl"
2024-08-16 17:50:38 +00:00
import Icon from "@lib/components/Icon.svelte"
2026-07-02 21:29:30 +00:00
import Spinner from "@lib/components/Spinner.svelte"
import {uploadFile} from "@app/uploads"
2026-07-02 21:29:30 +00:00
import {pushToast} from "@app/toast"
2024-08-08 23:32:46 +00:00
2025-02-04 00:37:14 +00:00
interface Props {
2025-02-04 04:43:18 +00:00
file?: File | undefined
url?: string | undefined
2025-02-04 00:37:14 +00:00
}
2025-02-04 04:43:18 +00:00
let {file = $bindable(), url = $bindable()}: Props = $props()
2024-08-08 23:32:46 +00:00
const id = randomId()
const onDragEnter = () => {
active = true
}
const onDragOver = () => {
active = true
}
const onDragLeave = () => {
active = false
}
2025-09-15 12:40:02 +00:00
const onDrop = async (e: any) => {
2024-08-08 23:32:46 +00:00
active = false
2025-09-18 19:17:31 +00:00
file = await compressFile(e.dataTransfer.files[0])
2024-08-08 23:32:46 +00:00
}
2025-09-15 12:40:02 +00:00
const onChange = async (e: any) => {
2025-09-18 19:17:31 +00:00
file = await compressFile(e.target.files[0])
2024-08-08 23:32:46 +00:00
}
const onClear = () => {
2025-02-04 04:43:18 +00:00
initialUrl = undefined
file = undefined
url = undefined
2024-08-08 23:32:46 +00:00
}
2025-02-04 00:37:14 +00:00
let active = $state(false)
2026-07-02 21:29:30 +00:00
let loading = $state(false)
2025-02-04 00:37:14 +00:00
let initialUrl = $state(url)
2024-08-08 23:32:46 +00:00
2025-02-04 03:28:29 +00:00
$effect(() => {
call(async () => {
if (file) {
2026-07-02 21:29:30 +00:00
loading = true
const {error, result} = await uploadFile(file)
loading = false
if (result?.url) {
url = result.url
} else {
2026-07-02 21:29:30 +00:00
file = undefined
pushToast({theme: "error", message: error || "Failed to upload image."})
}
} else {
url = initialUrl
}
})
2025-02-04 00:37:14 +00:00
})
2024-08-08 23:32:46 +00:00
</script>
<form>
2025-02-04 00:37:14 +00:00
<input {id} type="file" accept="image/*" onchange={onChange} class="hidden" />
2024-08-08 23:32:46 +00:00
<label
for={id}
aria-label="Drag and drop files here."
style="background-image: url({url}); border-color: {active ? 'var(--primary)' : 'var(--line)'};"
class="bg-surface-more relative flex h-24 w-24 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 border-solid bg-cover bg-center transition-all"
2024-10-03 19:27:41 +00:00
class:transparent={!url}
2025-02-04 00:37:14 +00:00
ondragenter={stopPropagation(preventDefault(onDragEnter))}
ondragover={stopPropagation(preventDefault(onDragOver))}
ondragleave={stopPropagation(preventDefault(onDragLeave))}
ondrop={stopPropagation(preventDefault(onDrop))}>
2026-07-02 21:29:30 +00:00
<button
onclick={url && stopPropagation(onClear)}
class="absolute right-0 top-0 h-5 w-5 overflow-hidden rounded-full flex items-center justify-center"
style="background: {url ? 'var(--error)' : 'var(--primary)'};">
2024-10-03 19:27:41 +00:00
{#if url}
2026-07-02 21:29:30 +00:00
<Icon icon={CloseCircle} class="scale-150 bg-surface-more!" />
2024-08-08 23:32:46 +00:00
{:else}
<Icon icon={AddCircle} class="scale-150 bg-surface-more!" />
2024-08-08 23:32:46 +00:00
{/if}
2026-07-02 21:29:30 +00:00
</button>
{#if loading}
<div class="absolute inset-0 flex items-center justify-center rounded-full bg-black/40">
<Spinner />
</div>
{:else if !url}
2025-09-10 22:26:39 +00:00
<Icon icon={GallerySend} size={7} />
2024-08-08 23:32:46 +00:00
{/if}
</label>
</form>