or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

constructor-config.mdevent-system.mdfile-management.mdindex.mdstatic-utilities.mdthumbnails.mdupload-processing.md
tile.json

thumbnails.mddocs/

Thumbnail Generation

Automatic image thumbnail creation with customizable dimensions, processing queue, and support for various image sources.

Capabilities

Thumbnail Creation

Generate thumbnails for image files with configurable dimensions and processing.

/**
 * Create thumbnail for a file
 * @param file - File object to create thumbnail for
 * @param callback - Optional callback function called when thumbnail is ready
 */
createThumbnail(file: File, callback?: (dataUrl: string) => void): void;

/**
 * Create thumbnail from image URL
 * @param file - File object associated with the thumbnail
 * @param imageUrl - URL of the image to create thumbnail from
 * @param callback - Optional callback function called when thumbnail is ready
 * @param crossOrigin - Cross-origin setting for image loading
 */
createThumbnailFromUrl(
  file: File, 
  imageUrl: string, 
  callback?: (dataUrl: string) => void, 
  crossOrigin?: string
): void;

Usage Examples:

const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true,
  thumbnailWidth: 200,
  thumbnailHeight: 200,
  maxThumbnailFilesize: 10 // MB
});

// Create thumbnail manually
dropzone.on("addedfile", function(file) {
  if (file.type.startsWith("image/")) {
    dropzone.createThumbnail(file, function(dataUrl) {
      console.log("Thumbnail created for:", file.name);
      
      // Use custom thumbnail display
      const img = document.createElement("img");
      img.src = dataUrl;
      img.className = "custom-thumbnail";
      file.previewElement.appendChild(img);
    });
  }
});

// Create thumbnail from URL
const file = { name: "external-image.jpg", type: "image/jpeg" };
dropzone.createThumbnailFromUrl(
  file,
  "https://example.com/image.jpg",
  function(dataUrl) {
    console.log("Thumbnail created from URL");
    // Display thumbnail
  },
  "anonymous" // CORS setting
);

Thumbnail Configuration

Configure thumbnail generation behavior and appearance.

interface ThumbnailOptions {
  /** Whether to create image thumbnails automatically */
  createImageThumbnails?: boolean;
  /** Maximum file size for thumbnail generation in MB */
  maxThumbnailFilesize?: number;
  /** Thumbnail width in pixels */
  thumbnailWidth?: number;
  /** Thumbnail height in pixels */
  thumbnailHeight?: number;
  /** Custom resize function for thumbnail dimensions */
  resize?: (file: File) => {
    srcX: number;      // Source X coordinate
    srcY: number;      // Source Y coordinate
    srcWidth: number;  // Source width
    srcHeight: number; // Source height
    trgX: number;      // Target X coordinate
    trgY: number;      // Target Y coordinate
    trgWidth: number;  // Target width
    trgHeight: number; // Target height
  };
}

Usage Examples:

// Basic thumbnail configuration
const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true,
  thumbnailWidth: 150,
  thumbnailHeight: 150,
  maxThumbnailFilesize: 5 // Only create thumbnails for files < 5MB
});

// Custom resize logic
const customResizeDropzone = new Dropzone("#custom-resize", {
  url: "/upload",
  createImageThumbnails: true,
  thumbnailWidth: 200,
  thumbnailHeight: 200,
  
  resize: function(file) {
    // Custom resize calculation for square thumbnails
    const size = Math.min(file.width, file.height);
    const x = (file.width - size) / 2;
    const y = (file.height - size) / 2;
    
    return {
      srcX: x,
      srcY: y,
      srcWidth: size,
      srcHeight: size,
      trgX: 0,
      trgY: 0,
      trgWidth: this.options.thumbnailWidth,
      trgHeight: this.options.thumbnailHeight
    };
  }
});

// Disable thumbnails for large files
const selectiveDropzone = new Dropzone("#selective-thumbnails", {
  url: "/upload",
  createImageThumbnails: true,
  maxThumbnailFilesize: 2, // 2MB limit
  
  init: function() {
    this.on("addedfile", function(file) {
      if (file.size > 2 * 1024 * 1024 && file.type.startsWith("image/")) {
        console.log("Skipping thumbnail for large file:", file.name);
        // Show placeholder instead
        const placeholder = document.createElement("div");
        placeholder.className = "thumbnail-placeholder";
        placeholder.textContent = "Large image";
        file.previewElement.appendChild(placeholder);
      }
    });
  }
});

Thumbnail Queue Management

Internal thumbnail processing queue to manage thumbnail generation efficiently.

/**
 * Add file to thumbnail generation queue
 * @param file - File to add to thumbnail queue
 */
_enqueueThumbnail(file: File): void;

/**
 * Process the thumbnail generation queue
 */
_processThumbnailQueue(): void;

// Internal properties
_thumbnailQueue: File[];     // Queue of files waiting for thumbnail generation
_processingThumbnail: boolean; // Whether a thumbnail is currently being processed

Usage Examples:

const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true,
  
  init: function() {
    // Monitor thumbnail queue
    const originalEnqueue = this._enqueueThumbnail;
    this._enqueueThumbnail = function(file) {
      console.log("Adding to thumbnail queue:", file.name);
      console.log("Queue length:", this._thumbnailQueue.length);
      originalEnqueue.call(this, file);
    };
    
    // Monitor queue processing
    const originalProcess = this._processThumbnailQueue;
    this._processThumbnailQueue = function() {
      console.log("Processing thumbnail queue, length:", this._thumbnailQueue.length);
      originalProcess.call(this);
    };
  }
});

// Custom thumbnail queue management
function prioritizeThumbnails(dropzone, priorityFiles) {
  // Move priority files to front of thumbnail queue
  priorityFiles.forEach(priorityFile => {
    const index = dropzone._thumbnailQueue.indexOf(priorityFile);
    if (index > 0) {
      dropzone._thumbnailQueue.splice(index, 1);
      dropzone._thumbnailQueue.unshift(priorityFile);
    }
  });
  
  // Process queue if not already processing
  if (!dropzone._processingThumbnail) {
    dropzone._processThumbnailQueue();
  }
}

Thumbnail Events

Events related to thumbnail generation and processing.

/**
 * Thumbnail generated successfully
 * @param file - File for which thumbnail was generated
 * @param dataUrl - Data URL of the generated thumbnail
 */
on("thumbnail", (file: File, dataUrl: string) => void): void;

Usage Examples:

const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true
});

// Handle thumbnail generation
dropzone.on("thumbnail", function(file, dataUrl) {
  console.log("Thumbnail ready for:", file.name);
  
  // Custom thumbnail display
  const container = file.previewElement;
  const img = container.querySelector("img") || document.createElement("img");
  img.src = dataUrl;
  img.className = "dropzone-thumbnail";
  img.alt = file.name;
  
  // Add thumbnail to container if not already there
  if (!container.contains(img)) {
    container.appendChild(img);
  }
  
  // Store thumbnail data on file object
  file.thumbnailData = dataUrl;
  
  // Optional: Create multiple thumbnail sizes
  createAdditionalThumbnails(file, dataUrl);
});

function createAdditionalThumbnails(file, originalDataUrl) {
  const sizes = [
    { width: 64, height: 64, name: "small" },
    { width: 128, height: 128, name: "medium" },
    { width: 256, height: 256, name: "large" }
  ];
  
  sizes.forEach(size => {
    resizeImage(originalDataUrl, size.width, size.height)
      .then(resizedData => {
        file[`thumbnail_${size.name}`] = resizedData;
        console.log(`${size.name} thumbnail created for:`, file.name);
      });
  });
}

function resizeImage(dataUrl, width, height) {
  return new Promise((resolve) => {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");
    const img = new Image();
    
    img.onload = function() {
      canvas.width = width;
      canvas.height = height;
      ctx.drawImage(img, 0, 0, width, height);
      resolve(canvas.toDataURL());
    };
    
    img.src = dataUrl;
  });
}

Advanced Thumbnail Features

Advanced thumbnail processing including iOS fixes and custom image handling.

// Internal utility functions (exposed for advanced usage)

/**
 * Detect vertical squash in iOS images (iOS 6/7 bug fix)
 * @param img - Image element to check
 */
detectVerticalSquash(img: HTMLImageElement): number;

/**
 * Draw image with iOS fixes applied
 * @param ctx - Canvas 2D context
 * @param img - Image to draw
 * @param sx - Source X coordinate
 * @param sy - Source Y coordinate
 * @param sw - Source width
 * @param sh - Source height
 * @param dx - Destination X coordinate
 * @param dy - Destination Y coordinate
 * @param dw - Destination width
 * @param dh - Destination height
 */
drawImageIOSFix(
  ctx: CanvasRenderingContext2D,
  img: HTMLImageElement,
  sx: number, sy: number, sw: number, sh: number,
  dx: number, dy: number, dw: number, dh: number
): void;

Usage Examples:

const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true,
  
  // Custom resize function with iOS handling
  resize: function(file) {
    const info = {
      srcX: 0,
      srcY: 0,
      srcWidth: file.width,
      srcHeight: file.height,
      trgX: 0,
      trgY: 0,
      trgWidth: this.options.thumbnailWidth,
      trgHeight: this.options.thumbnailHeight
    };
    
    // Apply aspect ratio preservation
    const sourceAspect = file.width / file.height;
    const targetAspect = info.trgWidth / info.trgHeight;
    
    if (sourceAspect > targetAspect) {
      // Source is wider, crop width
      info.srcWidth = file.height * targetAspect;
      info.srcX = (file.width - info.srcWidth) / 2;
    } else {
      // Source is taller, crop height
      info.srcHeight = file.width / targetAspect;
      info.srcY = (file.height - info.srcHeight) / 2;
    }
    
    return info;
  }
});

// Custom thumbnail processing with canvas
function createCustomThumbnail(file, callback) {
  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  const img = new Image();
  
  img.onload = function() {
    // Set canvas size
    canvas.width = 200;
    canvas.height = 200;
    
    // Handle iOS image squashing
    const verticalSquashRatio = detectVerticalSquash(img);
    
    // Draw with iOS fix
    drawImageIOSFix(
      ctx, img,
      0, 0, img.naturalWidth, img.naturalHeight,
      0, 0, 200, 200 / verticalSquashRatio
    );
    
    // Apply custom effects
    ctx.globalAlpha = 0.8;
    ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
    ctx.fillRect(0, 0, 200, 200);
    
    callback(canvas.toDataURL());
  };
  
  img.src = URL.createObjectURL(file);
}

// Use custom thumbnail processing
dropzone.on("addedfile", function(file) {
  if (file.type.startsWith("image/")) {
    createCustomThumbnail(file, function(dataUrl) {
      // Use the custom thumbnail
      file.customThumbnail = dataUrl;
    });
  }
});

Thumbnail Template Integration

Integration with Dropzone's preview template system.

// Default thumbnail template elements
interface ThumbnailTemplate {
  /** Main preview element containing thumbnail */
  previewElement: HTMLElement;
  /** Image element for thumbnail display */
  thumbnailElement: HTMLImageElement;
  /** Filename display element */
  filenameElement: HTMLElement;
  /** File size display element */
  filesizeElement: HTMLElement;
}

Usage Examples:

// Custom preview template with thumbnail integration
const dropzone = new Dropzone("#my-dropzone", {
  url: "/upload",
  createImageThumbnails: true,
  previewTemplate: `
    <div class="dz-preview dz-file-preview">
      <div class="dz-image">
        <img data-dz-thumbnail />
      </div>
      <div class="dz-details">
        <div class="dz-filename"><span data-dz-name></span></div>
        <div class="dz-size"><span data-dz-size></span></div>
      </div>
      <div class="dz-progress">
        <span class="dz-upload" data-dz-uploadprogress></span>
      </div>
      <div class="dz-error-message"><span data-dz-errormessage></span></div>
      <div class="dz-success-mark">✓</div>
      <div class="dz-error-mark">✗</div>
    </div>
  `,
  
  init: function() {
    this.on("thumbnail", function(file, dataUrl) {
      // Thumbnail automatically inserted into [data-dz-thumbnail] element
      console.log("Thumbnail integrated with template for:", file.name);
      
      // Add custom thumbnail effects
      const thumbnailEl = file.previewElement.querySelector("[data-dz-thumbnail]");
      if (thumbnailEl) {
        thumbnailEl.style.borderRadius = "8px";
        thumbnailEl.style.transition = "transform 0.2s";
        
        thumbnailEl.addEventListener("mouseover", function() {
          this.style.transform = "scale(1.05)";
        });
        
        thumbnailEl.addEventListener("mouseout", function() {
          this.style.transform = "scale(1)";
        });
      }
    });
  }
});