| <script lang="ts"> | |
| interface Props { | |
| onImageSelected: (image: File) => void; | |
| isProcessing?: boolean; | |
| } | |
| let { onImageSelected, isProcessing = false }: Props = $props(); | |
| let fileInput: HTMLInputElement; | |
| let dragActive = $state(false); | |
| let preview: string | null = $state(null); | |
| function handleFileSelect(e: Event) { | |
| const target = e.target as HTMLInputElement; | |
| if (target.files && target.files[0]) { | |
| processFile(target.files[0]); | |
| } | |
| } | |
| function handleDrop(e: DragEvent) { | |
| e.preventDefault(); | |
| dragActive = false; | |
| if (e.dataTransfer?.files && e.dataTransfer.files[0]) { | |
| processFile(e.dataTransfer.files[0]); | |
| } | |
| } | |
| function handleDragOver(e: DragEvent) { | |
| e.preventDefault(); | |
| dragActive = true; | |
| } | |
| function handleDragLeave(e: DragEvent) { | |
| e.preventDefault(); | |
| dragActive = false; | |
| } | |
| function processFile(file: File) { | |
| if (!file.type.startsWith('image/')) { | |
| alert('Please upload an image file'); | |
| return; | |
| } | |
| // Create preview | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| preview = e.target?.result as string; | |
| }; | |
| reader.readAsDataURL(file); | |
| onImageSelected(file); | |
| } | |
| function triggerFileSelect() { | |
| fileInput.click(); | |
| } | |
| </script> | |
| <div class="upload-container"> | |
| <h3>Upload Your Photo</h3> | |
| <p class="subtitle">Upload a photo that will inspire your monster creation</p> | |
| <div | |
| class="upload-area" | |
| class:drag-active={dragActive} | |
| class:has-preview={preview} | |
| ondrop={handleDrop} | |
| ondragover={handleDragOver} | |
| ondragleave={handleDragLeave} | |
| onclick={triggerFileSelect} | |
| onkeypress={(e) => e.key === 'Enter' && triggerFileSelect()} | |
| role="button" | |
| tabindex="0" | |
| > | |
| {#if preview} | |
| <img src={preview} alt="Preview" class="preview-image" /> | |
| <div class="overlay"> | |
| <p>Click to change image</p> | |
| </div> | |
| {:else} | |
| <svg class="upload-icon" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> | |
| <polyline points="17 8 12 3 7 8"></polyline> | |
| <line x1="12" y1="3" x2="12" y2="15"></line> | |
| </svg> | |
| <p class="upload-text">Drop an image here or click to upload</p> | |
| <p class="upload-hint">Supports JPG, PNG, GIF</p> | |
| {/if} | |
| </div> | |
| <input | |
| type="file" | |
| accept="image/*" | |
| onchange={handleFileSelect} | |
| bind:this={fileInput} | |
| class="hidden-input" | |
| disabled={isProcessing} | |
| /> | |
| </div> | |
| <style> | |
| .upload-container { | |
| max-width: 600px; | |
| margin: 0 auto; | |
| text-align: center; | |
| } | |
| h3 { | |
| margin-bottom: 0.5rem; | |
| color: #333; | |
| } | |
| .subtitle { | |
| color: #666; | |
| margin-bottom: 2rem; | |
| } | |
| .upload-area { | |
| border: 2px dashed #ccc; | |
| border-radius: 12px; | |
| padding: 3rem; | |
| background: #fafafa; | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .upload-area:hover { | |
| border-color: #007bff; | |
| background: #f0f7ff; | |
| } | |
| .upload-area.drag-active { | |
| border-color: #007bff; | |
| background: #e3f2ff; | |
| transform: scale(1.02); | |
| } | |
| .upload-area.has-preview { | |
| padding: 0; | |
| background: #fff; | |
| } | |
| .upload-icon { | |
| color: #007bff; | |
| margin-bottom: 1rem; | |
| } | |
| .upload-text { | |
| font-size: 1.1rem; | |
| color: #333; | |
| margin-bottom: 0.5rem; | |
| } | |
| .upload-hint { | |
| font-size: 0.9rem; | |
| color: #666; | |
| } | |
| .hidden-input { | |
| display: none; | |
| } | |
| .preview-image { | |
| width: 100%; | |
| height: 400px; | |
| object-fit: contain; | |
| display: block; | |
| } | |
| .overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0, 0, 0, 0.7); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .upload-area:hover .overlay { | |
| opacity: 1; | |
| } | |
| .overlay p { | |
| color: white; | |
| font-size: 1.1rem; | |
| } | |
| </style> |