101 lines
2.5 KiB
Svelte
101 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import { CloudArrowUpOutline } from 'flowbite-svelte-icons';
|
|
|
|
let {
|
|
mediaType = 'photo',
|
|
multiple = true,
|
|
onFilesSelected
|
|
}: {
|
|
mediaType?: 'photo' | 'video';
|
|
multiple?: boolean;
|
|
onFilesSelected: (files: FileList) => void;
|
|
} = $props();
|
|
|
|
let isDragging = $state(false);
|
|
let fileInput: HTMLInputElement;
|
|
|
|
const acceptTypes = mediaType === 'photo' ? 'image/*' : 'video/*';
|
|
const maxSize = mediaType === 'photo' ? '10MB' : '100MB';
|
|
const formats = mediaType === 'photo' ? 'JPG, PNG, GIF, WEBP' : 'MP4, MOV, WEBM';
|
|
|
|
function handleDrop(e: DragEvent) {
|
|
e.preventDefault();
|
|
isDragging = false;
|
|
|
|
const files = e.dataTransfer?.files;
|
|
if (files && files.length > 0) {
|
|
validateAndEmit(files);
|
|
}
|
|
}
|
|
|
|
function handleDragOver(e: DragEvent) {
|
|
e.preventDefault();
|
|
isDragging = true;
|
|
}
|
|
|
|
function handleDragLeave(e: DragEvent) {
|
|
e.preventDefault();
|
|
isDragging = false;
|
|
}
|
|
|
|
function handleFileSelect(e: Event) {
|
|
const input = e.target as HTMLInputElement;
|
|
if (input.files && input.files.length > 0) {
|
|
validateAndEmit(input.files);
|
|
}
|
|
}
|
|
|
|
function validateAndEmit(files: FileList) {
|
|
const validFiles: File[] = [];
|
|
const typePrefix = mediaType === 'photo' ? 'image/' : 'video/';
|
|
|
|
for (const file of files) {
|
|
if (file.type.startsWith(typePrefix)) {
|
|
validFiles.push(file);
|
|
}
|
|
}
|
|
|
|
if (validFiles.length > 0) {
|
|
const dt = new DataTransfer();
|
|
validFiles.forEach((f) => dt.items.add(f));
|
|
onFilesSelected(dt.files);
|
|
}
|
|
}
|
|
|
|
function openFileDialog() {
|
|
fileInput?.click();
|
|
}
|
|
</script>
|
|
|
|
<div
|
|
class="relative rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-8 text-center transition-colors dark:border-gray-600 dark:bg-gray-800 {isDragging
|
|
? 'border-primary-500 bg-primary-50 dark:border-primary-400 dark:bg-primary-900/20'
|
|
: 'hover:border-gray-400 dark:hover:border-gray-500'}"
|
|
ondrop={handleDrop}
|
|
ondragover={handleDragOver}
|
|
ondragleave={handleDragLeave}
|
|
role="button"
|
|
tabindex="0"
|
|
onclick={openFileDialog}
|
|
onkeydown={(e) => e.key === 'Enter' && openFileDialog()}
|
|
>
|
|
<input
|
|
bind:this={fileInput}
|
|
type="file"
|
|
accept={acceptTypes}
|
|
{multiple}
|
|
onchange={handleFileSelect}
|
|
class="hidden"
|
|
/>
|
|
|
|
<CloudArrowUpOutline class="mx-auto mb-4 h-12 w-12 text-gray-400 dark:text-gray-500" />
|
|
|
|
<p class="mb-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
{isDragging ? `Drop ${mediaType}s here` : `Drag & drop ${mediaType}s or click to browse`}
|
|
</p>
|
|
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
Max size: {maxSize} • Formats: {formats}
|
|
</p>
|
|
</div>
|