165 lines
4.6 KiB
Svelte
165 lines
4.6 KiB
Svelte
<script lang="ts">
|
|
import * as Dialog from '$lib/components/ui/dialog';
|
|
import { Button, buttonVariants } from '$lib/components/ui/button';
|
|
import { Input } from '$lib/components/ui/input';
|
|
import * as RadioGroup from '$lib/components/ui/radio-group';
|
|
import { Label } from '$lib/components/ui/label';
|
|
import * as Field from '$lib/components/ui/field';
|
|
import { fileToBase64 } from '$lib/shared';
|
|
import LoadingSpinner from './loading-spinner.svelte';
|
|
import ImageUpload from '$lib/components/custom/image-upload/image-upload.svelte';
|
|
import { genDescription } from '$lib/db/items.remote';
|
|
import { EMAIL_REGEX_STRING } from '$lib/consts';
|
|
import { Textarea } from '$lib/components/ui/textarea';
|
|
|
|
let itemLocation: string | undefined = $state('');
|
|
let foundLocation: string | undefined = $state();
|
|
let description: string | undefined = $state();
|
|
let isGenerating = $state(false);
|
|
|
|
async function resizeImage(
|
|
file: File,
|
|
maxWidth = 300,
|
|
maxHeight = 300,
|
|
quality = 0.8
|
|
): Promise<File> {
|
|
const bitmap = await createImageBitmap(file);
|
|
|
|
let { width, height } = bitmap;
|
|
|
|
const scale = Math.min(maxWidth / width, maxHeight / height, 1);
|
|
|
|
width = Math.round(width * scale);
|
|
height = Math.round(height * scale);
|
|
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
|
|
const ctx = canvas.getContext("2d")!;
|
|
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
|
|
const blob = await new Promise<Blob>((resolve) =>
|
|
canvas.toBlob((blob) => resolve(blob!), "image/jpeg", quality)
|
|
);
|
|
|
|
return new File([blob], file.name, {
|
|
type: "image/jpeg",
|
|
lastModified: Date.now()
|
|
});
|
|
}
|
|
|
|
async function onSelect(file: File) {
|
|
isGenerating = true;
|
|
|
|
const resized = await resizeImage(file, 300, 300, 0.7);
|
|
|
|
const base64 = await fileToBase64(resized);
|
|
|
|
description = await genDescription(base64);
|
|
|
|
isGenerating = false;
|
|
}
|
|
|
|
let { open = $bindable() }: { open: boolean } = $props();
|
|
</script>
|
|
|
|
<Dialog.Root bind:open>
|
|
<Dialog.Content onOpenAutoFocus={(e) => e.preventDefault()}>
|
|
<Dialog.Header>
|
|
<Dialog.Title>Submit Found Item</Dialog.Title>
|
|
<Dialog.Description>
|
|
Your item will need to be approved before becoming public.
|
|
</Dialog.Description>
|
|
</Dialog.Header>
|
|
|
|
<form method="post" action="/items?/create" enctype="multipart/form-data">
|
|
<Field.Group>
|
|
<ImageUpload onSelect={onSelect} required />
|
|
|
|
<Field.Field>
|
|
<Field.Label for="description">
|
|
Description <span class="text-error">*</span>
|
|
</Field.Label>
|
|
<Textarea
|
|
id="description"
|
|
name="description"
|
|
bind:value={description}
|
|
placeholder="A red leather book bag..."
|
|
maxlength={200}
|
|
rows={3}
|
|
required
|
|
/>
|
|
</Field.Field>
|
|
|
|
<Field.Field>
|
|
<Field.Label for="foundLocation">
|
|
Where did you find it?
|
|
</Field.Label>
|
|
<Input
|
|
id="foundLocation"
|
|
name="foundLocation"
|
|
bind:value={foundLocation}
|
|
placeholder="By the tennis courts."
|
|
/>
|
|
</Field.Field>
|
|
|
|
|
|
<RadioGroup.Root name="location" bind:value={itemLocation} required>
|
|
<Field.Label for="location">
|
|
Where is it? <span class="text-error">*</span>
|
|
</Field.Label>
|
|
<div class="flex items-center space-x-2">
|
|
<RadioGroup.Item value="finderPossession" id="finderPossession" />
|
|
<Label for="finderPossession">
|
|
I still have the item.
|
|
</Label>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-2">
|
|
<RadioGroup.Item value="turnedIn" id="turnedIn" />
|
|
<Label for="turnedIn">
|
|
I turned the item in to the school lost and found.
|
|
</Label>
|
|
</div>
|
|
</RadioGroup.Root>
|
|
|
|
<Field.Field
|
|
class={itemLocation !== "finderPossession" ? "hidden pointer-events-none opacity-50" : ""}
|
|
>
|
|
<Field.Label for="email">
|
|
Your Email <span class="text-error">*</span>
|
|
</Field.Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
placeholder="name@domain.com"
|
|
pattern={EMAIL_REGEX_STRING}
|
|
required={itemLocation === "finderPossession"}
|
|
/>
|
|
</Field.Field>
|
|
</Field.Group>
|
|
|
|
<Dialog.Footer class="mt-4">
|
|
<Dialog.Close
|
|
type="button"
|
|
class={buttonVariants({ variant: "outline" })}
|
|
>
|
|
Cancel
|
|
</Dialog.Close>
|
|
<Button type="submit">Submit</Button>
|
|
</Dialog.Footer>
|
|
</form>
|
|
</Dialog.Content>
|
|
</Dialog.Root>
|
|
|
|
|
|
<div
|
|
class="fixed inset-0 bg-black/85 z-[999999] w-screen h-screen flex justify-center items-center transition-opacity duration-150 flex-col md:flex-row"
|
|
class:opacity-0={!isGenerating}
|
|
class:pointer-events-none={!isGenerating}
|
|
>
|
|
<LoadingSpinner classes="w-28 md:w-48 mb-2 md:-mb-4" />
|
|
<p class="text-4xl md:text-6xl text-white">Loading...</p>
|
|
</div>
|