# ImageFilesField

A comprehensive image upload and management component that provides drag-and-drop upload functionality, image preview, thumbnail generation, and file management capabilities. Perfect for handling image galleries, avatar uploads, and document attachments with visual previews.

## Installation

```bash
npm install @ticatec/uniface-element
```

## Import

```typescript
import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
import type { ImageFile, RemoveConfirm } from "@ticatec/uniface-element/ImageFilesField";
import type { FileUpload } from "@ticatec/uniface-element";
```

## Basic Usage

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  
  let files = [];
  
  // Simple upload function
  const uploadImage = (file, progressUpdate, onUploaded, errorHandler) => {
    // Simulate upload progress
    let progress = 0;
    const interval = setInterval(() => {
      progress += 10;
      progressUpdate(Math.floor(file.size / 100 * progress));
      
      if (progress >= 100) {
        clearInterval(interval);
        // Call onUploaded with the uploaded file URL and thumbnail URL
        onUploaded(`/uploads/${file.name}`, `/thumbnails/${file.name}`);
      }
    }, 100);
    
    return {
      cancel: async () => {
        clearInterval(interval);
        return true;
      }
    };
  };
</script>

<ImageFilesField 
  bind:files={files}
  uploadFile={uploadImage}
  maxFiles={5}
/>
```

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `files` | `Array<ImageFile>` | `[]` | Array of uploaded image files |
| `uploadFile` | `FileUpload` | required | Upload function handler |
| `maxFiles` | `number` | `1` | Maximum number of files allowed |
| `variant` | `"" \| "plain" \| "outlined" \| "filled"` | `""` | Visual variant |
| `disabled` | `boolean` | `false` | Whether the component is disabled |
| `readonly` | `boolean` | `false` | Whether the component is read-only |
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode (Edit or View) |
| `removeFileConfirm` | `RemoveConfirm \| null` | `null` | Confirmation function for file removal |
| `style` | `string` | `""` | Additional CSS styles |
| `text` | `string \| null` | `null` | Text representation of files (auto-generated) |

## Types

### ImageFile

```typescript
interface ImageFile {
  name: string;        // File name
  uri: string;         // Full-size image URL
  thumbnail?: string;  // Thumbnail URL (optional)
}
```

### RemoveConfirm

```typescript
type RemoveConfirm = (file: ImageFile) => Promise<boolean>;
```

### FileUpload

```typescript
type FileUpload = (
  file: File,
  progressUpdate: ProgressUpdate,
  onUploaded: OnUploaded,
  errorHandler: ErrorHandler
) => UploadProgress;

type ProgressUpdate = (bytesUploaded: number) => void;
type OnUploaded = (url: string, thumbnail?: string) => void;
type ErrorHandler = (error: Error) => void;

interface UploadProgress {
  cancel: () => Promise<boolean>;
}
```

## Examples

### Basic Image Upload with Preview

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  import FormField from "@ticatec/uniface-element/FormField";
  
  let profileImages = [];
  
  const uploadProfileImage = (file, progressUpdate, onUploaded, errorHandler) => {
    const formData = new FormData();
    formData.append('image', file);
    
    const xhr = new XMLHttpRequest();
    
    xhr.upload.addEventListener('progress', (e) => {
      if (e.lengthComputable) {
        progressUpdate(e.loaded);
      }
    });
    
    xhr.onload = () => {
      if (xhr.status === 200) {
        const response = JSON.parse(xhr.responseText);
        onUploaded(response.url, response.thumbnail);
      } else {
        errorHandler(new Error(`Upload failed: ${xhr.statusText}`));
      }
    };
    
    xhr.onerror = () => {
      errorHandler(new Error('Network error during upload'));
    };
    
    xhr.open('POST', '/api/upload/profile-image');
    xhr.send(formData);
    
    return {
      cancel: async () => {
        xhr.abort();
        return true;
      }
    };
  };
  
  const confirmRemoveImage = async (file) => {
    return confirm(`Are you sure you want to remove ${file.name}?`);
  };
</script>

<div class="profile-section">
  <FormField label="Profile Pictures">
    <ImageFilesField 
      bind:files={profileImages}
      uploadFile={uploadProfileImage}
      removeFileConfirm={confirmRemoveImage}
      maxFiles={3}
      variant="outlined"
    />
  </FormField>
  
  <div class="image-info">
    <p>Uploaded {profileImages.length} image(s)</p>
    {#each profileImages as image}
      <p>• {image.name}</p>
    {/each}
  </div>
</div>

<style>
  .profile-section {
    max-width: 600px;
    margin: 20px;
    padding: 20px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
  
  .image-info {
    margin-top: 16px;
    padding: 12px;
    background: #f8fafc;
    border-radius: 6px;
  }
</style>
```

### Product Gallery Management

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  import FormField from "@ticatec/uniface-element/FormField";
  
  let productImages = [
    {
      name: "product-main.jpg",
      uri: "/images/products/product-main.jpg",
      thumbnail: "/images/products/thumbs/product-main.jpg"
    },
    {
      name: "product-side.jpg", 
      uri: "/images/products/product-side.jpg",
      thumbnail: "/images/products/thumbs/product-side.jpg"
    }
  ];
  
  let isUploading = false;
  let uploadProgress = {};
  
  const uploadProductImage = (file, progressUpdate, onUploaded, errorHandler) => {
    isUploading = true;
    const fileId = `${file.name}-${Date.now()}`;
    uploadProgress[fileId] = 0;
    
    // Simulate image processing and upload
    const totalSteps = 100;
    let currentStep = 0;
    
    const interval = setInterval(() => {
      currentStep += Math.random() * 5;
      
      if (currentStep >= totalSteps) {
        currentStep = totalSteps;
        clearInterval(interval);
        
        // Simulate successful upload
        const baseUrl = '/api/products/images';
        const imageUrl = `${baseUrl}/${fileId}.jpg`;
        const thumbnailUrl = `${baseUrl}/thumbs/${fileId}.jpg`;
        
        onUploaded(imageUrl, thumbnailUrl);
        delete uploadProgress[fileId];
        isUploading = false;
      }
      
      const bytesUploaded = Math.floor(file.size * (currentStep / totalSteps));
      progressUpdate(bytesUploaded);
      uploadProgress[fileId] = currentStep;
      uploadProgress = { ...uploadProgress };
    }, 50);
    
    return {
      cancel: async () => {
        clearInterval(interval);
        delete uploadProgress[fileId];
        uploadProgress = { ...uploadProgress };
        isUploading = false;
        return true;
      }
    };
  };
  
  const confirmRemoveProductImage = async (file) => {
    const result = await new Promise((resolve) => {
      const modal = document.createElement('div');
      modal.innerHTML = `
        <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000;">
          <div style="background: white; padding: 24px; border-radius: 8px; max-width: 400px;">
            <h3>Remove Image</h3>
            <p>Are you sure you want to remove <strong>${file.name}</strong>?</p>
            <p>This action cannot be undone.</p>
            <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
              <button id="cancel">Cancel</button>
              <button id="confirm" style="background: #dc2626; color: white;">Remove</button>
            </div>
          </div>
        </div>
      `;
      
      document.body.appendChild(modal);
      
      modal.querySelector('#cancel').onclick = () => {
        document.body.removeChild(modal);
        resolve(false);
      };
      
      modal.querySelector('#confirm').onclick = () => {
        document.body.removeChild(modal);
        resolve(true);
      };
    });
    
    return result;
  };
  
  $: totalImages = productImages.length;
  $: canAddMore = totalImages < 10;
</script>

<div class="product-gallery">
  <h2>Product Image Gallery</h2>
  
  <FormField label="Product Images (Max 10)" error={!canAddMore ? "Maximum 10 images allowed" : ""}>
    <ImageFilesField 
      bind:files={productImages}
      uploadFile={uploadProductImage}
      removeFileConfirm={confirmRemoveProductImage}
      maxFiles={10}
      variant="filled"
      disabled={!canAddMore}
    />
  </FormField>
  
  <div class="gallery-stats">
    <div class="stat-item">
      <span class="stat-label">Total Images:</span>
      <span class="stat-value">{totalImages}/10</span>
    </div>
    
    {#if isUploading}
      <div class="upload-status">
        <span>Uploading images...</span>
        <div class="progress-indicators">
          {#each Object.entries(uploadProgress) as [fileId, progress]}
            <div class="progress-bar">
              <div class="progress-fill" style="width: {progress}%"></div>
            </div>
          {/each}
        </div>
      </div>
    {/if}
  </div>
  
  <div class="image-list">
    <h3>Current Images</h3>
    {#each productImages as image, index}
      <div class="image-item">
        <img src={image.thumbnail || image.uri} alt={image.name} width="50" height="50" />
        <div class="image-details">
          <span class="image-name">{image.name}</span>
          <span class="image-position">Position {index + 1}</span>
        </div>
      </div>
    {/each}
  </div>
</div>

<style>
  .product-gallery {
    max-width: 800px;
    margin: 20px;
    padding: 24px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
  
  .gallery-stats {
    margin: 16px 0;
    padding: 12px;
    background: #f8fafc;
    border-radius: 6px;
  }
  
  .stat-item {
    display: flex;
    justify-content: space-between;
    margin-bottom: 8px;
  }
  
  .stat-label {
    font-weight: 500;
  }
  
  .stat-value {
    color: #3b82f6;
    font-weight: 600;
  }
  
  .upload-status {
    margin-top: 12px;
  }
  
  .progress-indicators {
    margin-top: 8px;
  }
  
  .progress-bar {
    height: 4px;
    background: #e5e7eb;
    border-radius: 2px;
    overflow: hidden;
    margin: 4px 0;
  }
  
  .progress-fill {
    height: 100%;
    background: #3b82f6;
    transition: width 0.3s ease;
  }
  
  .image-list {
    margin-top: 20px;
  }
  
  .image-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px;
    border-bottom: 1px solid #f3f4f6;
  }
  
  .image-item img {
    border-radius: 4px;
    object-fit: cover;
  }
  
  .image-details {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  
  .image-name {
    font-weight: 500;
  }
  
  .image-position {
    font-size: 0.875rem;
    color: #6b7280;
  }
</style>
```

### Avatar Upload with Cropping

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  import FormField from "@ticatec/uniface-element/FormField";
  import { DisplayMode } from "@ticatec/uniface-element";
  
  let userAvatar = [];
  let displayMode = DisplayMode.Edit;
  
  const uploadAvatar = (file, progressUpdate, onUploaded, errorHandler) => {
    // Validate file type
    if (!file.type.startsWith('image/')) {
      errorHandler(new Error('Please select an image file'));
      return { cancel: async () => true };
    }
    
    // Validate file size (max 5MB)
    if (file.size > 5 * 1024 * 1024) {
      errorHandler(new Error('Image must be smaller than 5MB'));
      return { cancel: async () => true };
    }
    
    // Create image for validation
    const img = new Image();
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    return new Promise((resolve) => {
      img.onload = () => {
        // Set canvas size for square thumbnail
        const size = 200;
        canvas.width = size;
        canvas.height = size;
        
        // Calculate crop dimensions (center crop to square)
        const minDimension = Math.min(img.width, img.height);
        const x = (img.width - minDimension) / 2;
        const y = (img.height - minDimension) / 2;
        
        // Draw cropped and resized image
        ctx.drawImage(img, x, y, minDimension, minDimension, 0, 0, size, size);
        
        // Simulate upload progress
        let progress = 0;
        const interval = setInterval(() => {
          progress += 10;
          progressUpdate(Math.floor(file.size * (progress / 100)));
          
          if (progress >= 100) {
            clearInterval(interval);
            
            // Convert canvas to blob and create URLs
            canvas.toBlob((blob) => {
              const thumbnailUrl = URL.createObjectURL(blob);
              const fullUrl = URL.createObjectURL(file);
              
              onUploaded(fullUrl, thumbnailUrl);
            }, 'image/jpeg', 0.8);
          }
        }, 100);
        
        resolve({
          cancel: async () => {
            clearInterval(interval);
            return true;
          }
        });
      };
      
      img.onerror = () => {
        errorHandler(new Error('Invalid image file'));
        resolve({ cancel: async () => true });
      };
      
      img.src = URL.createObjectURL(file);
    });
  };
  
  const confirmRemoveAvatar = async (file) => {
    return confirm('Remove your profile picture?');
  };
  
  const toggleMode = () => {
    displayMode = displayMode === DisplayMode.Edit ? DisplayMode.View : DisplayMode.Edit;
  };
  
  $: hasAvatar = userAvatar.length > 0;
</script>

<div class="avatar-section">
  <div class="avatar-header">
    <h2>Profile Picture</h2>
    <button on:click={toggleMode} class="mode-toggle">
      {displayMode === DisplayMode.Edit ? 'Preview Mode' : 'Edit Mode'}
    </button>
  </div>
  
  <div class="avatar-container">
    <FormField label="Upload Avatar">
      <ImageFilesField 
        bind:files={userAvatar}
        uploadFile={uploadAvatar}
        removeFileConfirm={confirmRemoveAvatar}
        maxFiles={1}
        variant="outlined"
        {displayMode}
        style="min-height: 200px; display: flex; align-items: center; justify-content: center;"
      />
    </FormField>
    
    {#if !hasAvatar && displayMode === DisplayMode.Edit}
      <div class="avatar-placeholder">
        <div class="placeholder-icon">👤</div>
        <p>Click to upload your profile picture</p>
        <p class="placeholder-hint">Recommended: Square image, max 5MB</p>
      </div>
    {/if}
  </div>
  
  {#if hasAvatar}
    <div class="avatar-info">
      <h3>Current Avatar</h3>
      <div class="avatar-details">
        <img 
          src={userAvatar[0].thumbnail || userAvatar[0].uri} 
          alt="Avatar preview"
          class="avatar-preview"
        />
        <div class="avatar-metadata">
          <p><strong>File:</strong> {userAvatar[0].name}</p>
          <p><strong>Status:</strong> ✅ Uploaded successfully</p>
        </div>
      </div>
    </div>
  {/if}
</div>

<style>
  .avatar-section {
    max-width: 500px;
    margin: 20px;
    padding: 24px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
  
  .avatar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 20px;
  }
  
  .mode-toggle {
    padding: 6px 12px;
    background: #f3f4f6;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    cursor: pointer;
  }
  
  .avatar-container {
    position: relative;
  }
  
  .avatar-placeholder {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: none;
  }
  
  .placeholder-icon {
    font-size: 48px;
    margin-bottom: 12px;
  }
  
  .placeholder-hint {
    font-size: 0.875rem;
    color: #6b7280;
  }
  
  .avatar-info {
    margin-top: 20px;
    padding: 16px;
    background: #f8fafc;
    border-radius: 8px;
  }
  
  .avatar-details {
    display: flex;
    align-items: center;
    gap: 16px;
  }
  
  .avatar-preview {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    border: 3px solid white;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }
  
  .avatar-metadata p {
    margin: 4px 0;
  }
</style>
```

### Document Attachments with Thumbnails

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  import FormField from "@ticatec/uniface-element/FormField";
  
  let documentImages = [];
  let uploadErrors = [];
  
  const uploadDocument = (file, progressUpdate, onUploaded, errorHandler) => {
    // Clear previous errors
    uploadErrors = [];
    
    // Validate file type (images only for this example)
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    if (!allowedTypes.includes(file.type)) {
      const error = new Error(`Unsupported file type: ${file.type}. Please upload JPEG, PNG, GIF, or WebP images.`);
      errorHandler(error);
      uploadErrors = [...uploadErrors, error.message];
      return { cancel: async () => true };
    }
    
    // Simulate document processing (OCR, thumbnail generation, etc.)
    let progress = 0;
    const stages = [
      'Uploading file...',
      'Generating thumbnail...',
      'Processing document...',
      'Saving to database...',
      'Complete!'
    ];
    let currentStage = 0;
    
    const interval = setInterval(() => {
      progress += Math.random() * 15;
      
      if (progress > (currentStage + 1) * 20 && currentStage < stages.length - 1) {
        currentStage++;
        console.log(stages[currentStage]);
      }
      
      if (progress >= 100) {
        progress = 100;
        clearInterval(interval);
        
        // Simulate successful upload
        const documentId = Date.now().toString();
        const documentUrl = `/api/documents/${documentId}`;
        const thumbnailUrl = `/api/documents/${documentId}/thumbnail`;
        
        onUploaded(documentUrl, thumbnailUrl);
      }
      
      progressUpdate(Math.floor(file.size * (progress / 100)));
    }, 200);
    
    return {
      cancel: async () => {
        clearInterval(interval);
        console.log('Upload cancelled');
        return true;
      }
    };
  };
  
  const confirmRemoveDocument = async (file) => {
    const userConfirmed = confirm(`Remove document "${file.name}"?\n\nThis will permanently delete the document and its thumbnail.`);
    
    if (userConfirmed) {
      // Simulate API call to delete document
      console.log('Deleting document:', file.name);
      // You could add actual deletion logic here
    }
    
    return userConfirmed;
  };
  
  $: documentCount = documentImages.length;
  $: hasErrors = uploadErrors.length > 0;
</script>

<div class="document-section">
  <h2>Document Attachments</h2>
  
  <FormField label="Upload Documents" error={hasErrors ? uploadErrors.join(', ') : ''}>
    <ImageFilesField 
      bind:files={documentImages}
      uploadFile={uploadDocument}
      removeFileConfirm={confirmRemoveDocument}
      maxFiles={20}
      variant="filled"
    />
  </FormField>
  
  <div class="document-summary">
    <div class="summary-stats">
      <div class="stat">
        <span class="stat-number">{documentCount}</span>
        <span class="stat-label">Documents</span>
      </div>
      <div class="stat">
        <span class="stat-number">{20 - documentCount}</span>
        <span class="stat-label">Remaining</span>
      </div>
    </div>
    
    {#if hasErrors}
      <div class="error-panel">
        <h4>Upload Errors</h4>
        {#each uploadErrors as error}
          <p class="error-message">❌ {error}</p>
        {/each}
      </div>
    {/if}
  </div>
  
  {#if documentCount > 0}
    <div class="document-list">
      <h3>Uploaded Documents</h3>
      <div class="document-grid">
        {#each documentImages as doc, index}
          <div class="document-card">
            <div class="document-thumbnail">
              <img src={doc.thumbnail || doc.uri} alt={doc.name} />
              <div class="document-index">{index + 1}</div>
            </div>
            <div class="document-info">
              <p class="document-name" title={doc.name}>{doc.name}</p>
              <p class="document-status">✅ Processed</p>
            </div>
          </div>
        {/each}
      </div>
    </div>
  {/if}
</div>

<style>
  .document-section {
    max-width: 800px;
    margin: 20px;
    padding: 24px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
  
  .document-summary {
    margin: 16px 0;
  }
  
  .summary-stats {
    display: flex;
    gap: 24px;
    margin-bottom: 16px;
  }
  
  .stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 12px;
    background: #f8fafc;
    border-radius: 8px;
    min-width: 80px;
  }
  
  .stat-number {
    font-size: 1.5rem;
    font-weight: 600;
    color: #3b82f6;
  }
  
  .stat-label {
    font-size: 0.875rem;
    color: #6b7280;
  }
  
  .error-panel {
    padding: 12px;
    background: #fef2f2;
    border: 1px solid #fecaca;
    border-radius: 6px;
  }
  
  .error-message {
    margin: 4px 0;
    color: #dc2626;
    font-size: 0.875rem;
  }
  
  .document-list {
    margin-top: 24px;
  }
  
  .document-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 16px;
    margin-top: 12px;
  }
  
  .document-card {
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    overflow: hidden;
    background: white;
  }
  
  .document-thumbnail {
    position: relative;
    height: 120px;
    background: #f9fafb;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  
  .document-thumbnail img {
    max-width: 100%;
    max-height: 100%;
    object-fit: cover;
  }
  
  .document-index {
    position: absolute;
    top: 8px;
    right: 8px;
    background: #3b82f6;
    color: white;
    border-radius: 50%;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.75rem;
    font-weight: 600;
  }
  
  .document-info {
    padding: 12px;
  }
  
  .document-name {
    margin: 0 0 4px 0;
    font-weight: 500;
    font-size: 0.875rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  
  .document-status {
    margin: 0;
    font-size: 0.75rem;
    color: #16a34a;
  }
</style>
```

### Custom Upload Progress and Error Handling

```svelte
<script>
  import ImageFilesField from "@ticatec/uniface-element/ImageFilesField";
  import FormField from "@ticatec/uniface-element/FormField";
  
  let galleryImages = [];
  let activeUploads = new Map();
  let uploadHistory = [];
  
  const uploadWithErrorHandling = (file, progressUpdate, onUploaded, errorHandler) => {
    const uploadId = Date.now() + Math.random();
    
    // Add to active uploads tracking
    activeUploads.set(uploadId, {
      file: file.name,
      startTime: new Date(),
      progress: 0
    });
    activeUploads = new Map(activeUploads);
    
    // Simulate various upload scenarios
    const scenarios = [
      { success: true, delay: 2000 },   // Success after 2s
      { success: false, error: 'Network timeout', delay: 3000 },  // Network error
      { success: false, error: 'Server error 500', delay: 1500 }, // Server error
      { success: true, delay: 4000 },   // Slow success
    ];
    
    const scenario = scenarios[Math.floor(Math.random() * scenarios.length)];
    let progress = 0;
    let cancelled = false;
    
    const interval = setInterval(() => {
      if (cancelled) {
        clearInterval(interval);
        return;
      }
      
      progress += Math.random() * 20;
      
      // Update tracking
      if (activeUploads.has(uploadId)) {
        activeUploads.get(uploadId).progress = Math.min(progress, 95);
        activeUploads = new Map(activeUploads);
      }
      
      progressUpdate(Math.floor(file.size * (Math.min(progress, 95) / 100)));
      
      if (progress >= 100) {
        clearInterval(interval);
        
        setTimeout(() => {
          if (cancelled) return;
          
          // Remove from active uploads
          activeUploads.delete(uploadId);
          activeUploads = new Map(activeUploads);
          
          if (scenario.success) {
            // Successful upload
            const imageUrl = `/uploads/${uploadId}_${file.name}`;
            const thumbnailUrl = `/uploads/thumbs/${uploadId}_${file.name}`;
            
            onUploaded(imageUrl, thumbnailUrl);
            
            // Add to history
            uploadHistory = [{
              file: file.name,
              status: 'success',
              timestamp: new Date(),
              message: 'Upload completed successfully'
            }, ...uploadHistory];
          } else {
            // Failed upload
            const error = new Error(scenario.error);
            errorHandler(error);
            
            // Add to history
            uploadHistory = [{
              file: file.name,
              status: 'error',
              timestamp: new Date(),
              message: scenario.error
            }, ...uploadHistory];
          }
        }, scenario.delay - (progress > 95 ? (progress - 95) * 50 : 0));
      }
    }, 100);
    
    return {
      cancel: async () => {
        cancelled = true;
        clearInterval(interval);
        
        // Remove from active uploads
        activeUploads.delete(uploadId);
        activeUploads = new Map(activeUploads);
        
        // Add to history
        uploadHistory = [{
          file: file.name,
          status: 'cancelled',
          timestamp: new Date(),
          message: 'Upload cancelled by user'
        }, ...uploadHistory];
        
        return true;
      }
    };
  };
  
  const confirmRemoveGalleryImage = async (file) => {
    const shouldRemove = confirm(`Remove "${file.name}" from gallery?`);
    
    if (shouldRemove) {
      // Add to history
      uploadHistory = [{
        file: file.name,
        status: 'removed',
        timestamp: new Date(),
        message: 'Image removed from gallery'
      }, ...uploadHistory];
    }
    
    return shouldRemove;
  };
  
  const clearHistory = () => {
    uploadHistory = [];
  };
  
  const formatTime = (date) => {
    return date.toLocaleTimeString();
  };
  
  const getStatusIcon = (status) => {
    const icons = {
      success: '✅',
      error: '❌',
      cancelled: '⏹️',
      removed: '🗑️'
    };
    return icons[status] || '📄';
  };
  
  $: hasActiveUploads = activeUploads.size > 0;
  $: totalImages = galleryImages.length;
</script>

<div class="advanced-upload">
  <h2>Advanced Image Upload</h2>
  
  <FormField label="Gallery Images">
    <ImageFilesField 
      bind:files={galleryImages}
      uploadFile={uploadWithErrorHandling}
      removeFileConfirm={confirmRemoveGalleryImage}
      maxFiles={15}
      variant="outlined"
    />
  </FormField>
  
  {#if hasActiveUploads}
    <div class="active-uploads">
      <h3>Active Uploads</h3>
      {#each Array.from(activeUploads.entries()) as [id, upload]}
        <div class="upload-item">
          <div class="upload-info">
            <span class="upload-filename">{upload.file}</span>
            <span class="upload-time">Started: {formatTime(upload.startTime)}</span>
          </div>
          <div class="upload-progress">
            <div class="progress-bar">
              <div class="progress-fill" style="width: {upload.progress}%"></div>
            </div>
            <span class="progress-text">{Math.round(upload.progress)}%</span>
          </div>
        </div>
      {/each}
    </div>
  {/if}
  
  <div class="gallery-stats">
    <div class="stat-row">
      <span>Total Images: {totalImages}/15</span>
      <span>Active Uploads: {activeUploads.size}</span>
    </div>
  </div>
  
  {#if uploadHistory.length > 0}
    <div class="upload-history">
      <div class="history-header">
        <h3>Upload History</h3>
        <button on:click={clearHistory} class="clear-history">Clear</button>
      </div>
      
      <div class="history-list">
        {#each uploadHistory.slice(0, 10) as entry}
          <div class="history-item" class:error={entry.status === 'error'}>
            <span class="history-icon">{getStatusIcon(entry.status)}</span>
            <div class="history-details">
              <span class="history-file">{entry.file}</span>
              <span class="history-message">{entry.message}</span>
            </div>
            <span class="history-time">{formatTime(entry.timestamp)}</span>
          </div>
        {/each}
      </div>
    </div>
  {/if}
</div>

<style>
  .advanced-upload {
    max-width: 800px;
    margin: 20px;
    padding: 24px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
  }
  
  .active-uploads {
    margin: 20px 0;
    padding: 16px;
    background: #fef3c7;
    border-radius: 8px;
    border-left: 4px solid #f59e0b;
  }
  
  .upload-item {
    display: flex;
    align-items: center;
    gap: 16px;
    margin: 8px 0;
  }
  
  .upload-info {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  
  .upload-filename {
    font-weight: 500;
  }
  
  .upload-time {
    font-size: 0.75rem;
    color: #6b7280;
  }
  
  .upload-progress {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 120px;
  }
  
  .progress-bar {
    flex: 1;
    height: 6px;
    background: #e5e7eb;
    border-radius: 3px;
    overflow: hidden;
  }
  
  .progress-fill {
    height: 100%;
    background: #3b82f6;
    transition: width 0.3s ease;
  }
  
  .progress-text {
    font-size: 0.875rem;
    font-weight: 500;
    min-width: 40px;
  }
  
  .gallery-stats {
    margin: 16px 0;
    padding: 12px;
    background: #f8fafc;
    border-radius: 6px;
  }
  
  .stat-row {
    display: flex;
    justify-content: space-between;
    font-weight: 500;
  }
  
  .upload-history {
    margin-top: 24px;
  }
  
  .history-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 12px;
  }
  
  .clear-history {
    padding: 4px 8px;
    background: #f3f4f6;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.875rem;
  }
  
  .history-list {
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    overflow: hidden;
  }
  
  .history-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    border-bottom: 1px solid #f3f4f6;
    background: white;
  }
  
  .history-item:last-child {
    border-bottom: none;
  }
  
  .history-item.error {
    background: #fef2f2;
  }
  
  .history-icon {
    font-size: 1.25rem;
  }
  
  .history-details {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  
  .history-file {
    font-weight: 500;
    font-size: 0.875rem;
  }
  
  .history-message {
    font-size: 0.75rem;
    color: #6b7280;
  }
  
  .history-time {
    font-size: 0.75rem;
    color: #9ca3af;
    min-width: 80px;
    text-align: right;
  }
</style>
```

## Styling

The ImageFilesField component can be styled using CSS custom properties:

```css
.uniface-images-field {
  --border-color: #e2e8f0;
  --background-color: #ffffff;
  --hover-background: #f9fafb;
  --upload-icon-color: #6b7280;
  --preview-overlay: rgba(0, 0, 0, 0.7);
}

/* Variant-specific styling */
.uniface-common-editor.outlined {
  border: 2px dashed var(--border-color);
  border-radius: 8px;
  padding: 20px;
}

.uniface-common-editor.filled {
  background-color: #f8fafc;
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 20px;
}

/* Upload area styling */
.editor-action-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  border: 2px dashed #d1d5db;
  border-radius: 8px;
  cursor: pointer;
  transition: all 0.2s;
}

.editor-action-icon:hover {
  border-color: #3b82f6;
  background-color: #f0f9ff;
}
```

## Accessibility

The ImageFilesField component includes several accessibility features:

- Keyboard navigation support
- ARIA labels for screen readers
- Focus management for interactive elements
- Alt text support for images
- Screen reader announcements for upload progress

## Best Practices

1. **File Validation**: Always validate file types and sizes before upload
2. **Error Handling**: Provide clear error messages and retry mechanisms
3. **Progress Feedback**: Show upload progress and allow cancellation
4. **Thumbnail Generation**: Generate appropriate thumbnails for performance
5. **Security**: Validate files on both client and server side
6. **Storage**: Implement proper file storage and cleanup strategies
7. **Performance**: Consider lazy loading for large image galleries

## License

MIT