diff --git a/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte b/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte index 1219c1b7..de2423af 100644 --- a/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte +++ b/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte @@ -10,10 +10,10 @@ import { t } from '$lib/utils/functions/translations'; import { uploadImage } from '$lib/utils/services/upload'; - import * as FileDropZone from '@cio/ui/custom/file-drop-zone'; - import type { FileRejectedReason } from '@cio/ui/custom/file-drop-zone'; + import * as ImageCropper from '@cio/ui/custom/image-cropper'; import { InputField } from '@cio/ui/custom/input-field'; import { Button } from '@cio/ui/base/button'; + import UploadCloudIcon from '@lucide/svelte/icons/upload-cloud'; interface Props { imageURL?: string; @@ -27,10 +27,15 @@ { label: 'Unsplash', value: 'unsplash' } ]; + // Course cover preview is rendered in a 280×200 frame, so crop uploads to + // that ratio instead of storing the raw image and letting CSS squeeze it. + const COVER_ASPECT = 280 / 200; + let isUploading = $state(false); let isSearching = $state(false); let currentTab = $state(tabs[0].value); let searchQuery = $state(''); + let cropperSrc = $state(''); let unsplashImages: { id: string | number; user: { @@ -43,7 +48,7 @@ alt_description: string; }[] = $state([]); - const MAX_IMAGE_SIZE = 500 * FileDropZone.KILOBYTE; + const MAX_IMAGE_SIZE = 500 * 1000; async function handleImageClick(img: string) { onchange?.(img); @@ -52,18 +57,22 @@ $handleOpenWidget.open = false; } - async function handleFilesUpload(files: File[]) { - const file = files[0]; - if (file) await handleUploadImage(file); + function handleUnsupportedFile(file: File) { + if (file.size > MAX_IMAGE_SIZE) { + snackbar.error('snackbar.landing_page_settings.error.file_size'); + return; + } + + snackbar.error('snackbar.landing_page_settings.error.file_size'); } - function handleFileRejected({ reason }: { reason: FileRejectedReason }) { - if (reason === 'Maximum file size exceeded') { - snackbar.error('snackbar.landing_page_settings.error.file_size'); - } else { - snackbar.error('snackbar.landing_page_settings.error.file_size'); - } - } + const handleCropped = async (croppedUrl: string) => { + const response = await fetch(croppedUrl); + const blob = await response.blob(); + const file = new File([blob], 'course-cover.png', { type: blob.type }); + + await handleUploadImage(file); + }; const handleUploadImage = async (image: File) => { isUploading = true; @@ -117,19 +126,34 @@ - - $t('course.navItem.landing_page.upload_widget.size')} - /> - + + + + {$t('course.navItem.landing_page.upload_widget.drag_drop')} + + + {$t('course.navItem.landing_page.upload_widget.size')} + + + + + + + + + + + diff --git a/packages/ui/src/custom/image-cropper/image-cropper-upload-trigger.svelte b/packages/ui/src/custom/image-cropper/image-cropper-upload-trigger.svelte index d80067a2..53b42970 100644 --- a/packages/ui/src/custom/image-cropper/image-cropper-upload-trigger.svelte +++ b/packages/ui/src/custom/image-cropper/image-cropper-upload-trigger.svelte @@ -3,11 +3,13 @@ import type { ImageCropperUploadTriggerProps } from './types'; import { cn } from '../../tools'; - let { ref = $bindable(null), children, ...rest }: ImageCropperUploadTriggerProps = $props(); + let { ref = $bindable(null), class: className, children, ...rest }: ImageCropperUploadTriggerProps = $props(); const triggerState = useImageCropperTrigger(); const isDisabled = $derived(triggerState.rootState.disabled); + let isDragging = $state(false); + function handleClick(e: MouseEvent) { if (isDisabled) { e.preventDefault(); @@ -15,6 +17,28 @@ return false; } } + + function handleDragOver(e: DragEvent) { + if (isDisabled) return; + + e.preventDefault(); + isDragging = true; + } + + function handleDragLeave() { + isDragging = false; + } + + function handleDrop(e: DragEvent) { + isDragging = false; + if (isDisabled) return; + + const file = e.dataTransfer?.files?.[0]; + if (!file) return; + + e.preventDefault(); + triggerState.rootState.onUpload(file); + } {@render children?.()}
+ {$t('course.navItem.landing_page.upload_widget.drag_drop')} +
+ {$t('course.navItem.landing_page.upload_widget.size')} +