Skip to content

Latest commit

 

History

History
489 lines (423 loc) · 13.8 KB

IMAGE_STACK.md

File metadata and controls

489 lines (423 loc) · 13.8 KB

Synesthesia Image Stack

Core Technologies

  • Cloudinary: Primary image storage and transformation service
    • Handles image transformations, optimization, and CDN delivery
    • Used for all public-facing images
  • Active Storage: Database tracking for image attachments
    • Manages relationships between images and models
    • Handles local file storage during upload process
  • Service Worker: Offline caching and progressive loading
    • Caches Cloudinary URLs and responses
    • Manages offline fallback images
  • IndexedDB: Offline image upload queue
    • Stores pending uploads when offline
    • Manages local image previews

File Structure and Technology Mapping

app/
├── javascript/
│   ├── components/
│   │   ├── images/
│   │   │   ├── LazyImage.tsx       # Progressive loading component
│   │   │   │                       # Uses: Cloudinary + Service Worker
│   │   │   │                       # Purpose: Optimized image loading with fallbacks
│   │   │   │
│   │   │   └── ImageUploader.tsx   # Drag & drop upload component
│   │   │                           # Uses: Active Storage + Cloudinary + IndexedDB
│   │   │                           # Purpose: Handles file uploads with offline support
│   │   │
│   │   └── common/
│   │       └── Avatar.tsx          # Reusable avatar component
│   │                               # Uses: Cloudinary + Service Worker
│   │                               # Purpose: Displays user avatars with caching
│   │
│   ├── services/
│   │   ├── imageUpload.ts          # Image upload and transformation service
│   │   │                           # Uses: Cloudinary API
│   │   │                           # Purpose: Handles image transformations and uploads
│   │   │
│   │   └── offlineImageService.ts  # Offline storage handling
│   │                               # Uses: IndexedDB
│   │                               # Purpose: Manages offline image queue
│   │
│   └── service-worker.ts           # Service worker for caching
│                                   # Uses: Service Worker + Cloudinary Cache
│                                   # Purpose: Offline image availability
│
└── services/
    ├── image_service.rb            # Server-side image processing
    │                               # Uses: Cloudinary API
    │                               # Purpose: Server-side image optimizations
    │
    └── default_image_service.rb    # Default image management
                                    # Uses: Cloudinary + Active Storage
                                    # Purpose: Manages fallback images

Components and Their Technology Stack

1. LazyImage Component

Location: app/javascript/components/images/LazyImage.tsx Technology Stack:

  • Primary: Cloudinary (image delivery and transformations)
  • Secondary: Service Worker (offline caching)
  • Supporting: React (UI rendering)

Purpose:

  • Progressive image loading with placeholders
  • Automatic error handling
  • Optimized image loading with caching
  • Responsive image delivery

Implementation Notes:

import React, { useState, useEffect } from 'react'
// Uses Cloudinary URL generation
import { generateImageUrl } from '../../services/imageUpload'

interface Props {
  publicId: string  // Cloudinary public ID
  width?: number    // Cloudinary transformation parameter
  height?: number   // Cloudinary transformation parameter
  quality?: string  // Cloudinary quality setting
  alt?: string
  className?: string
  onLoad?: () => void
}

const LazyImage: React.FC<Props> = ({
  publicId,  // Cloudinary resource identifier
  width,
  height,
  quality = 'auto',  // Cloudinary auto-quality
  alt = '',
  className = '',
  onLoad
}) => {
  const [loaded, setLoaded] = useState(false)
  const [error, setError] = useState(false)

  useEffect(() => {
    const img = new Image()
    img.src = generateImageUrl(publicId, { width, height, quality })
    
    img.onload = () => {
      setLoaded(true)
      onLoad?.()
    }
    
    img.onerror = () => {
      setError(true)
    }
  }, [publicId, width, height, quality, onLoad])

  return (
    <div className={`lazy-image ${loaded ? 'loaded' : ''} ${className}`}>
      <img
        src={generateImageUrl(publicId, { width, height, quality })}
        alt={alt}
        style={{ opacity: loaded ? 1 : 0 }}
      />
      {!loaded && <div className="image-placeholder" />}
    </div>
  )
}

2. Image Upload System

Location: app/javascript/components/images/ImageUploader.tsx Technology Stack:

  • Primary: Active Storage (initial upload handling)
  • Secondary: Cloudinary (final storage and optimization)
  • Supporting:
    • IndexedDB (offline storage)
    • React Dropzone (file input)
    • Service Worker (cache management)

Purpose:

  • Handle file uploads with drag & drop
  • Manage offline uploads
  • Process and optimize images
  • Provide upload status feedback

Implementation Notes:

import React, { useState, useCallback, useEffect } from 'react'
import { useDropzone } from 'react-dropzone'
// Uses both Cloudinary and Active Storage
import { uploadImage } from '../../services/imageUpload'
// Uses IndexedDB for offline storage
import { offlineImageService } from '../../services/offlineImageService'

const ImageUploader: React.FC<Props> = ({ type, onUpload }) => {
  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    const file = acceptedFiles[0]
    if (!navigator.onLine) {
      // Uses IndexedDB for offline storage
      const localUrl = await offlineImageService.storePendingUpload(file, type)
      onUpload(localUrl)
    } else {
      // Uses Active Storage for upload, then Cloudinary for storage
      const response = await uploadImage(file, type)
      onUpload(response.url)  // Cloudinary URL
    }
  }, [type, onUpload])

  return (
    <div {...getRootProps()}>
      <input {...getInputProps()} />
      {/* Drag & drop UI */}
    </div>
  )
}

3. Offline Storage Service

Location: app/javascript/services/offlineImageService.ts Technology Stack:

  • Primary: IndexedDB (client-side storage)
  • Supporting:
    • URL.createObjectURL (local preview)
    • Promise-based async handling

Purpose:

  • Store pending uploads during offline mode
  • Manage upload queue
  • Handle local image previews
  • Sync with server when online

Implementation Notes:

import { openDB } from 'idb'

// Pure IndexedDB implementation for offline storage
class OfflineImageService {
  private db: Promise<IDBPDatabase>

  constructor() {
    this.db = this.initDB()
  }

  // Stores files locally until online
  async storePendingUpload(file: File, type: 'avatar' | 'gallery'): Promise<string> {
    const db = await this.db
    const localUrl = URL.createObjectURL(file)
    
    await db.add('pending-uploads', {
      file,
      type,
      localUrl,
      timestamp: Date.now()
    })
    return localUrl
  }

  async processPendingUploads(uploadFn: (file: File, type: string) => Promise<string>) {
    const db = await this.db
    const pending = await db.getAll('pending-uploads')
    
    for (const upload of pending) {
      await uploadFn(upload.file, upload.type)
      await db.delete('pending-uploads', upload.id)
      URL.revokeObjectURL(upload.localUrl)
    }
  }
}

4. Server-Side Image Processing

Location: app/services/image_service.rb Technology Stack:

  • Primary: Cloudinary API (image processing)
  • Secondary: Ruby/Rails (service layer)
  • Supporting: Active Storage (temporary storage)

Purpose:

  • Optimize images for different use cases
  • Generate multiple image variants
  • Apply face detection for avatars
  • Ensure consistent image quality

Implementation Notes:

class ImageService
  # Direct Cloudinary API usage for image optimization
  def self.optimize_avatar(image)
    Cloudinary::Uploader.upload(
      image,
      folder: 'avatars',
      transformation: [
        # Cloudinary-specific transformations
        { width: 500, height: 500, crop: :fill, gravity: :face },
        { quality: 'auto:good', fetch_format: :auto }
      ],
      eager: [
        # Cloudinary eager transformations for different sizes
        { width: 100, height: 100, crop: :thumb, gravity: :face },
        { width: 300, height: 300, crop: :thumb, gravity: :face }
      ]
    )
  end

  def self.optimize_gallery_image(image)
    Cloudinary::Uploader.upload(
      image,
      folder: 'gallery',
      transformation: [
        { width: 1200, height: 1200, crop: :limit },
        { quality: 'auto:good', fetch_format: :auto }
      ]
    )
  end
end

5. Default Image Management

Location: app/services/default_image_service.rb Technology Stack:

  • Primary: Cloudinary (image storage)
  • Secondary: Active Storage (local file handling)
  • Supporting: SVG (default image format)

Purpose:

  • Provide fallback images
  • Ensure default assets are available
  • Manage image transformations
  • Handle missing image scenarios

Implementation Notes:

class DefaultImageService
  # Maps local files to Cloudinary public IDs
  DEFAULT_IMAGES = {
    avatar: {
      url: 'app/assets/images/default-avatar.svg',  # Local file
      public_id: 'defaults/avatar'                  # Cloudinary ID
    },
    profile: {
      url: 'app/assets/images/default-profile.svg', # Local file
      public_id: 'defaults/profile'                 # Cloudinary ID
    }
  }

  def self.ensure_default_images
    DEFAULT_IMAGES.each do |type, image_data|
      ensure_default_image(type, image_data)
    end
  end

  private

  def self.upload_default_image(type, image_data)
    Cloudinary::Uploader.upload(
      image_data[:url],
      public_id: image_data[:public_id],
      overwrite: true,
      transformation: default_image_transformations(type)
    )
  end
end

6. User Model Integration

Location: app/models/user.rb Technology Stack:

  • Primary: Active Storage (attachment management)
  • Secondary: Cloudinary (image storage)
  • Supporting: Rails URL helpers

Purpose:

  • Manage user image associations
  • Handle image attachment logic
  • Provide fallback mechanisms
  • Generate appropriate URLs

Implementation Notes:

class User < ApplicationRecord
  # Active Storage attachment
  has_one_attached :avatar

  def avatar_url
    if avatar.attached?
      # Uses Active Storage URL helper
      Rails.application.routes.url_helpers.url_for(avatar)
    else
      # Falls back to Cloudinary default image
      DefaultImageService.get_default_image_url(:avatar)
    end
  end

  def profile_image_url
    if profile_image.attached?
      Rails.application.routes.url_helpers.url_for(profile_image)
    else
      DefaultImageService.get_default_image_url(:profile)
    end
  end
end

Usage Examples by Technology

Complete Upload Flow

# Technology Stack: Active Storage → Cloudinary → Service Worker
class ProfileImageUploader
  def upload(file)
    # 1. Active Storage: Initial upload
    attachment = user.avatar.attach(file)
    
    # 2. Cloudinary: Process and store
    cloudinary_url = ImageService.optimize_avatar(attachment)
    
    # 3. Service Worker: Cache for offline
    cache_image(cloudinary_url)
    
    # 4. IndexedDB: Store metadata
    store_image_metadata(cloudinary_url)
  end
end

Offline-First Image Display

// Technology Stack: Service Worker → IndexedDB → Cloudinary
const ImageDisplay: React.FC<Props> = ({ imageId }) => {
  const [imageUrl, setImageUrl] = useState<string>()
  
  useEffect(() => {
    // 1. Try Service Worker cache
    const cached = await caches.match(`/images/${imageId}`)
    if (cached) return setImageUrl(cached)
    
    // 2. Try IndexedDB
    const stored = await offlineImageService.getImage(imageId)
    if (stored) return setImageUrl(stored)
    
    // 3. Fallback to Cloudinary
    const cloudinaryUrl = generateImageUrl(imageId)
    setImageUrl(cloudinaryUrl)
  }, [imageId])
  
  return <LazyImage src={imageUrl} />
}

Technology-Specific Features

Cloudinary Features

  • Automatic format conversion (WebP, AVIF)
  • Face detection for avatars
  • Responsive image sizing
  • Quality optimization
  • CDN delivery

Active Storage Features

  • Database tracking of attachments
  • Model associations
  • Local file handling
  • Temporary storage during upload

Service Worker Features

  • Offline image caching
  • Network request interception
  • Cache management
  • Fallback image serving

IndexedDB Features

  • Offline file storage
  • Upload queue management
  • Local URL generation
  • Automatic sync when online

Performance Considerations by Technology

Cloudinary Optimization

  • Automatic format selection based on browser support
  • Dynamic quality adjustment
  • Responsive image generation
  • Metadata stripping

Active Storage Optimization

  • Background job processing
  • Variant caching
  • Database indexing
  • Blob deduplication

Service Worker Optimization

  • Selective caching strategies
  • Cache size management
  • Network-first for fresh content
  • Cache-first for static assets

IndexedDB Optimization

  • Batch processing of pending uploads
  • Automatic cleanup of old entries
  • Memory-efficient blob handling
  • Upload retry management

Advanced Features Implementation

1. Service Worker Caching

Location: app/javascript/service-worker.ts

const IMAGE_CACHE_NAME = 'cloudinary-images-v1'

self.addEventListener('fetch', (event: FetchEvent) => {
  if (event.request.url.includes('res.cloudinary.com')) {
    event.respondWith(
      caches.open(IMAGE_CACHE_NAME).then((cache) => {
        return cache.match(event.request).then((response) => {
          const fetchPromise = fetch(event.request).then((networkResponse) => {
            cache.put(event.request, networkResponse.clone())
            return networkResponse
          })
          return response || fetchPromise
        })
      })
    )
  }
})

Usage Examples

Profile Image Upload