From 750a4da34a1cbf015b55a763ca801840bca08107 Mon Sep 17 00:00:00 2001 From: adeliptr Date: Sun, 8 Jun 2025 14:43:44 +0900 Subject: [PATCH] fix uploadcare config --- src/lib/components/NewMemoryPopup.svelte | 316 ++++++++++++++++++----- src/lib/components/WorldMap.svelte | 40 ++- 2 files changed, 281 insertions(+), 75 deletions(-) diff --git a/src/lib/components/NewMemoryPopup.svelte b/src/lib/components/NewMemoryPopup.svelte index b7323de..2f7b52c 100644 --- a/src/lib/components/NewMemoryPopup.svelte +++ b/src/lib/components/NewMemoryPopup.svelte @@ -6,11 +6,7 @@ import { Loader } from '@googlemaps/js-api-loader'; import { ref, push, onValue } from 'firebase/database'; import { db } from '../../firebase'; - import * as UC from '@uploadcare/file-uploader'; - import "@uploadcare/file-uploader/web/uc-file-uploader-regular.min.css" - - UC.defineComponents(UC); - + import { UploadClient } from '@uploadcare/upload-client'; export let showPopup = false; export let onCancel = () => {}; @@ -18,20 +14,22 @@ let startDate = ''; let endDate = ''; let isGoogleLoaded = false; + let dragActive = false; let customLocation = ''; let customLocationInput: HTMLInputElement; + let images: File[] = []; + let imageUrls: string[] = []; let showLocationError = false; let showImageError = false; let hasAttemptedSubmit = false; let isFormValid = true; let selectedTripId = ''; //for dropdown let selectedLocation = ''; - let uploaderCtxEl: HTMLElement; - let uploaderCtx: any; let tripOptions: { value: string; label: string }[] = []; - let uploadedImageURLs: string[] = []; + // Initialize Uploadcare client + const uploadcare = new UploadClient({ publicKey: import.meta.env.VITE_UPLOADCARE_PUBLIC_KEY }); onMount(() => { // reference to the trips node @@ -55,29 +53,19 @@ tripOptions = options; }); }); + - $: if (uploaderCtx) { - uploaderCtx.on('change', () => { - const urls = uploaderCtx.files().map(file => file?.cdnUrl).filter(Boolean); - uploadedImageURLs = urls; - console.log('uploadedImageURLs:', uploadedImageURLs); - - if (uploadedImageURLs.length > 0) { - showImageError = false; - hasAttemptedSubmit = true; - } - }); - } - - $: isFormValid = ( - (selectedLocation !== '' && (!isCustomLocation() || customLocation.trim() !== '')) && - uploadedImageURLs.length > 0 - ); + $: if (hasAttemptedSubmit) { + isFormValid = ( + (selectedLocation !== '' && (!isCustomLocation() || customLocation.trim() !== '')) && + images.length > 0 + ); + } $: if (selectedTripId && selectedTripId !== 'custom') { const trip = tripOptions.find(t => t.value === selectedTripId); if (trip) { - selectedLocation = trip.label.split(' (')[0]; + selectedLocation = trip.label.split(' (')[0]; // label에서 name 추출 const tripRef = ref(db, `trips/${selectedTripId}`); onValue(tripRef, (snapshot) => { const val = snapshot.val(); @@ -126,29 +114,87 @@ } }); } - - function reset() { - showPopup = false; - selectedLocation = ''; - customLocation = ''; - startDate = ''; - endDate = ''; - showLocationError = false; - showImageError = false; - uploadedImageURLs = []; - hasAttemptedSubmit = false; - } + async function uploadImage(file: File): Promise { + try { + const result = await uploadcare.uploadFile(file); + return `https://ucarecdn.com/${result.uuid}/`; + } catch (error) { + console.error('Upload failed:', error); + throw error; + } + } + + async function handleInputChange(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files) { + const newFiles = Array.from(input.files); + images = [...images, ...newFiles]; + + isUploading = true; + try { + const newUrls = await Promise.all(newFiles.map(uploadImage)); + imageUrls = [...imageUrls, ...newUrls]; + } catch (error) { + // Handle error (show error message to user) + console.error('Upload failed:', error); + } finally { + isUploading = false; + } + } + } + + async function handleDrop(event: DragEvent) { + event.preventDefault(); + dragActive = false; + + const droppedFiles = Array.from(event.dataTransfer?.files || []) + .filter(file => file.type.startsWith('image/')); + + if (droppedFiles.length > 0) { + images = [...images, ...droppedFiles]; + + isUploading = true; + try { + const newUrls = await Promise.all(droppedFiles.map(uploadImage)); + imageUrls = [...imageUrls, ...newUrls]; + } catch (error) { + // Handle error (show error message to user) + console.error('Upload failed:', error); + } finally { + isUploading = false; + } + } + } + + function removeImage(img: File) { + const index = images.indexOf(img); + if (index > -1) { + images = images.filter((_, i) => i !== index); + imageUrls = imageUrls.filter((_, i) => i !== index); + } + } + function handleCancelClick() { onCancel(); reset(); } + function handleDragOver(event: DragEvent) { + event.preventDefault(); + dragActive = true; + } + + function handleDragLeave(event: DragEvent) { + event.preventDefault(); + dragActive = false; + } + async function handleAddMemory() { hasAttemptedSubmit = true; showLocationError = selectedLocation === '' || (isCustomLocation() && customLocation.trim() === ''); - showImageError = uploadedImageURLs.length === 0; + showImageError = images.length === 0; if (showLocationError || showImageError) return; @@ -159,19 +205,12 @@ location: finalLocation, startDate, endDate, - images: uploadedImageURLs, + images: imageUrls, createdAt: new Date().toISOString() }; try { console.log(`tid = ${selectedTripId}`); const memoryRef = ref(db, `trips/${selectedTripId}/memories`); - const newMemory = { - location: finalLocation, - startDate: startDate, - endDate: endDate, - images: uploadedImageURLs, - createdAt: new Date().toISOString() - }; const addedRef = await push(memoryRef, newMemory); reset(); @@ -180,7 +219,20 @@ console.error('Error saving memory:', error); } } - + + function reset() { + showPopup = false; + selectedLocation = ''; + customLocation = ''; + images = []; + imageUrls = []; + startDate = ''; + endDate = ''; + showLocationError = false; + showImageError = false; + } + + let isUploading = false; {#if showPopup} @@ -237,26 +289,47 @@

Please enter a location.

{/if} -
+
+ - - - - - - +
+ + +
document.getElementById('fileInput')?.click()}> + {#if images.length === 0} + Drop image here + {:else} +
+ {#each images as img, i} +
+ + {img.name} +

{img.name}

+
+ {/each} +
+ {/if} +
+ {#if isUploading} +
+
+

Uploading...

+
+ {/if} +
{#if showImageError}

Please upload at least one image.

{/if} @@ -266,7 +339,7 @@
@@ -369,6 +442,78 @@ margin-bottom: 0; } + .preview-list { + display: flex; + flex-wrap: wrap; + gap: 1rem; + max-height: 200px; + overflow-y: auto; + padding-right: 0.5rem; + } + + .preview-item { + position: relative; + width: 80px; + display: flex; + flex-direction: column; + align-items: center; + } + + .preview-item img { + width: 100%; + border-radius: 6px; + object-fit: cover; + } + + .preview-item p { + font-size: 0.75rem; + text-align: center; + margin-top: 0.3rem; + color: var(--gray-400); + } + + .delete-button { + position: absolute; + top: 4px; + right: 4px; + background: rgba(38, 38, 38, 0.5); + border: none; + color: var(--white); + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 0.9rem; + cursor: pointer; + z-index: 2; + } + + + .drop-area { + width: 100%; + min-height: 120px; + background: var(--gray-900); + border: 1px solid var(--gray-200); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: var(--white); + transition: border-color 0.2s; + cursor: pointer; + } + + .drop-area.active { + border-color: var(--memory-500); + color: var(--memory-500); + } + + .drop-label { + padding: 2rem 1rem; + text-align: center; + width: 100%; + cursor: pointer; + } + option.custom-option { color: var(--memory-500); } @@ -378,4 +523,39 @@ gap: 1rem; margin-top: 2rem; } + + .drop-area.uploading { + position: relative; + pointer-events: none; + opacity: 0.8; + } + + .upload-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; + } + + .spinner { + width: 30px; + height: 30px; + border: 3px solid var(--gray-200); + border-top-color: var(--planner-400); + border-radius: 50%; + animation: spin 1s linear infinite; + } + + @keyframes spin { + to { + transform: rotate(360deg); + } + } \ No newline at end of file diff --git a/src/lib/components/WorldMap.svelte b/src/lib/components/WorldMap.svelte index 5c4073a..eb96152 100644 --- a/src/lib/components/WorldMap.svelte +++ b/src/lib/components/WorldMap.svelte @@ -117,9 +117,11 @@ .attr('class', 'marker') .attr('fill', Colors.planner.med400) .on('click', function (event, d) { - d3.selectAll('.trip-label').remove(); - d3.selectAll('.trip-marker').remove(); - d3.selectAll('.trip-line').remove(); + d3.selectAll('.trip-label, .trip-marker, .trip-line') + .transition() + .duration(200) + .style('opacity', 0) + .remove(); event.stopPropagation(); const key = `${d.location.lat},${d.location.lng}`; @@ -139,7 +141,11 @@ .attr('y2', baseY - (trips.length - 1) * 24 + 6) .attr('stroke', Colors.planner.med400) .attr('stroke-width', 2) - .attr('class', 'trip-line'); + .attr('class', 'trip-line') + .style('opacity', 0) + .transition() + .duration(150) + .style('opacity', 1); } trips.forEach((trip, idx) => { @@ -169,14 +175,22 @@ .attr('font-size', '12px') .attr('class', 'trip-label') .text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`); + + markerGroup + .style('opacity', 0) + .transition() + .duration(200) + .style('opacity', 1); }); } }); svg.on('click', () => { - d3.selectAll('.trip-label').remove(); - d3.selectAll('.trip-marker').remove(); - d3.selectAll('.trip-line').remove(); + d3.selectAll('.trip-label, .trip-marker, .trip-line') + .transition() + .duration(200) + .style('opacity', 0) + .remove(); }); const zoom = d3.zoom() @@ -256,4 +270,16 @@ :global(.dark .marker) { fill: var(--memory-500); } + + :global(.dark .trip-marker) { + fill: var(--memory-500); + } + + :global(.dark .trip-line) { + stroke: var(--memory-500); + } + + :global(.dark .trip-label) { + fill: white; + }