feat: multi-image uploads #35
@@ -12,13 +12,20 @@
|
||||
let carousel = $state<HTMLDivElement>();
|
||||
let scrollPosition = $state(0);
|
||||
let scrollWidth = $state(0);
|
||||
let clientWidth = $state(0);
|
||||
let clientWidth = $state(1);
|
||||
function updateScroll() {
|
||||
scrollPosition = carousel?.scrollLeft || 0;
|
||||
scrollWidth = carousel?.scrollWidth || 0;
|
||||
clientWidth = carousel?.clientWidth || 0;
|
||||
clientWidth = carousel?.clientWidth || 1;
|
||||
}
|
||||
onMount(updateScroll);
|
||||
onMount(() => {
|
||||
const id = setInterval(() => {
|
||||
if (carousel) {
|
||||
updateScroll();
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearInterval(id);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="controls">
|
||||
@@ -37,7 +44,8 @@
|
||||
{#if scrollPosition > clientWidth / 2}
|
||||
<button
|
||||
class="arrow left"
|
||||
onclick={() => {
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
if (carousel) carousel.scrollLeft -= carousel.clientWidth;
|
||||
}}
|
||||
>
|
||||
@@ -47,7 +55,8 @@
|
||||
{#if scrollPosition < scrollWidth - clientWidth * 1.5}
|
||||
<button
|
||||
class="arrow right"
|
||||
onclick={() => {
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
if (carousel) carousel.scrollLeft += carousel.clientWidth;
|
||||
}}
|
||||
>
|
||||
@@ -95,12 +104,14 @@
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 9999px;
|
||||
}
|
||||
.arrow {
|
||||
.arrow,
|
||||
.delete {
|
||||
cursor: pointer;
|
||||
width: 2rem;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.arrow img {
|
||||
.arrow img,
|
||||
.delete img {
|
||||
width: 1.4rem;
|
||||
}
|
||||
.arrow:hover {
|
||||
@@ -120,14 +131,14 @@
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.delete {
|
||||
top: 0.1rem;
|
||||
right: 0.1rem;
|
||||
top: 0.4rem;
|
||||
right: 0.2rem;
|
||||
}
|
||||
.position {
|
||||
font-size: 0.8rem;
|
||||
bottom: 0.7rem;
|
||||
top: 0.4rem;
|
||||
border-radius: 1rem;
|
||||
right: 0.2rem;
|
||||
left: 0.2rem;
|
||||
padding: 0.3rem 0.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import cameraUrl from "$lib/assets/camera.svg";
|
||||
import Carousel from "../Carousel.svelte";
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
@@ -18,14 +19,37 @@
|
||||
class:no-bg={files && files.length > 0}
|
||||
>
|
||||
{#if files && files.length > 0}
|
||||
<img src={URL.createObjectURL(files[0])} alt="uploaded study space" class="preview" />
|
||||
<Carousel
|
||||
urls={files
|
||||
? Array(files.length)
|
||||
.keys()
|
||||
.map((i) => URL.createObjectURL(files![i]))
|
||||
.toArray()
|
||||
: []}
|
||||
ondelete={(idx) => {
|
||||
if (!files) return;
|
||||
const dt = new DataTransfer();
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (i !== idx) dt.items.add(files[i]);
|
||||
}
|
||||
files = dt.files;
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="message">
|
||||
<img src={cameraUrl} class="icon" alt="camera icon" />
|
||||
<span>Click to upload a photo</span>
|
||||
</div>
|
||||
{/if}
|
||||
<input type="file" id={name} {name} accept=".png, .jpg, .jpeg, .svg" {...rest} bind:files />
|
||||
<input
|
||||
type="file"
|
||||
id={name}
|
||||
{name}
|
||||
multiple
|
||||
accept=".png, .jpg, .jpeg, .svg"
|
||||
{...rest}
|
||||
bind:files
|
||||
/>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
@@ -5,13 +5,13 @@
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import crossUrl from "$lib/assets/cross.svg";
|
||||
import Button from "$lib/components/Button.svelte";
|
||||
import Image from "$lib/components/inputs/Image.svelte";
|
||||
import Images from "$lib/components/inputs/Images.svelte";
|
||||
import type { Table } from "$lib";
|
||||
|
||||
const { data } = $props();
|
||||
const { supabase } = $derived(data);
|
||||
|
||||
let spaceImg = $state<FileList>();
|
||||
let spaceImgs = $state<FileList>();
|
||||
let studySpaceData = $state<Omit<Table<"study_spaces">, "id" | "created_at" | "updated_at">>({
|
||||
description: "",
|
||||
building_location: "",
|
||||
@@ -19,8 +19,7 @@
|
||||
});
|
||||
|
||||
async function uploadStudySpace() {
|
||||
const imageFile = spaceImg?.[0];
|
||||
if (!imageFile) return alert("Please select an image file.");
|
||||
if (!spaceImgs || spaceImgs.length < 1) return alert("Please select an image file.");
|
||||
|
||||
const { data: studySpaceInsert, error: studySpaceError } = await supabase
|
||||
.from("study_spaces")
|
||||
@@ -30,21 +29,31 @@
|
||||
if (studySpaceError)
|
||||
return alert(`Error uploading study space: ${studySpaceError.message}`);
|
||||
|
||||
const { data: imgUpload, error: imageError } = await supabase.storage
|
||||
const imgUploads = await Promise.all(
|
||||
Array(spaceImgs.length)
|
||||
.keys()
|
||||
.map(async (i) => {
|
||||
const imageFile = spaceImgs![i];
|
||||
const resp = await supabase.storage
|
||||
.from("files_bucket")
|
||||
.upload(`public/${studySpaceInsert.id}-${imageFile.name}`, imageFile, {
|
||||
contentType: imageFile.type
|
||||
});
|
||||
return resp;
|
||||
})
|
||||
);
|
||||
const imageError = imgUploads.find(({ error }) => error)?.error;
|
||||
if (imageError) return alert(`Error uploading image: ${imageError.message}`);
|
||||
|
||||
const { error: imageInsertError } = await supabase
|
||||
.from("study_space_images")
|
||||
.insert({
|
||||
.insert(
|
||||
imgUploads.map(({ data }) => ({
|
||||
study_space_id: studySpaceInsert.id,
|
||||
image_path: imgUpload.path
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
image_path: data!.path
|
||||
}))
|
||||
)
|
||||
.select();
|
||||
if (imageInsertError) return alert(`Error creating image: ${imageInsertError.message}`);
|
||||
|
||||
alert("Thank you for your contribution!");
|
||||
@@ -67,7 +76,7 @@
|
||||
await uploadStudySpace();
|
||||
}}
|
||||
>
|
||||
<Image name="study-space-image" minHeight="16rem" bind:files={spaceImg} required />
|
||||
<Images name="study-space-image" minHeight="16rem" bind:files={spaceImgs} required />
|
||||
|
||||
<label for="location">Enter the name:</label>
|
||||
<Text
|
||||
@@ -97,7 +106,7 @@
|
||||
<div class="submit">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={(spaceImg?.length || 0) === 0 ||
|
||||
disabled={(spaceImgs?.length || 0) === 0 ||
|
||||
!studySpaceData.location ||
|
||||
!studySpaceData.description ||
|
||||
!studySpaceData.building_location}
|
||||
|
||||
Reference in New Issue
Block a user