feat: youtube preview

This commit is contained in:
2026-01-29 21:05:41 -03:00
parent 5125bbf4d4
commit bdb1759c81
40 changed files with 5056 additions and 411 deletions

View File

@@ -0,0 +1,278 @@
/**
* YouTube Design Preview - TypeScript Contracts
*
* These interfaces define the data structures used in the preview tool.
* This file serves as the contract between components.
*/
// ============================================================================
// Enums
// ============================================================================
/**
* Preview display modes matching YouTube's layout variations
*/
export type PreviewMode = 'desktop' | 'sidebar' | 'mobile';
/**
* Theme modes matching YouTube's color schemes
*/
export type ThemeMode = 'light' | 'dark';
/**
* Supported image formats for thumbnail upload
*/
export type SupportedImageFormat = 'image/jpeg' | 'image/png' | 'image/webp';
// ============================================================================
// Core Entities
// ============================================================================
/**
* Video metadata displayed alongside the thumbnail
*/
export interface VideoMetadata {
/** Video title (1-100 characters) */
title: string;
/** Channel name (1-50 characters) */
channelName: string;
/** Optional channel avatar URL or base64 data URL */
channelAvatarUrl?: string;
/** Duration in format "MM:SS" or "H:MM:SS" */
duration: string;
/** View count as raw number (formatted for display) */
viewCount: number;
/** Publication date (used for relative time display) */
publishedAt: Date;
}
/**
* Uploaded thumbnail image with metadata
*/
export interface UploadedThumbnail {
/** Unique identifier (UUID) */
id: string;
/** Base64 data URL of the image */
dataUrl: string;
/** Original filename */
originalName: string;
/** File size in bytes (max 5MB = 5,242,880) */
fileSize: number;
/** MIME type */
mimeType: SupportedImageFormat;
/** Original image dimensions */
width: number;
height: number;
/** Upload timestamp */
uploadedAt: Date;
}
// ============================================================================
// State Management
// ============================================================================
/**
* Complete preview state persisted to localStorage
*/
export interface PreviewState {
/** Currently active thumbnail for preview */
currentThumbnail: UploadedThumbnail | null;
/** Active preview layout mode */
previewMode: PreviewMode;
/** Active color theme */
themeMode: ThemeMode;
/** Video metadata for display */
metadata: VideoMetadata;
/** Recently used thumbnails (max 10) */
recentThumbnails: UploadedThumbnail[];
/** UI state: metadata editor visibility */
showMetadataEditor: boolean;
/** Last persistence timestamp */
lastSaved: Date;
/** Schema version for migrations */
version: number;
}
/**
* localStorage serialization wrapper
*/
export interface LocalStorageSchema {
version: 1;
state: PreviewState;
}
// ============================================================================
// Visual Testing
// ============================================================================
/**
* Result of a visual comparison test
*/
export interface VisualTestResult {
/** Unique test run identifier */
testId: string;
/** Preview mode being tested */
previewMode: PreviewMode;
/** Theme mode being tested */
themeMode: ThemeMode;
/** Test execution timestamp */
capturedAt: Date;
/** Similarity percentage (0-100) */
matchPercentage: number;
/** Pass/fail based on 98% threshold */
passed: boolean;
/** Path to captured screenshot */
actualScreenshotPath: string;
/** Path to reference screenshot */
expectedScreenshotPath: string;
/** Path to diff image (only if failed) */
diffImagePath?: string;
/** Error message if test failed */
errorMessage?: string;
}
/**
* YouTube reference screenshot metadata
*/
export interface ReferenceScreenshot {
/** Unique identifier */
id: string;
/** Preview mode this reference is for */
previewMode: PreviewMode;
/** Theme mode this reference is for */
themeMode: ThemeMode;
/** When captured from YouTube */
capturedAt: Date;
/** YouTube URL captured from */
capturedFrom: string;
/** File system path */
filePath: string;
/** Viewport dimensions */
viewportWidth: number;
viewportHeight: number;
/** Browser used for capture */
browserType: 'chromium' | 'firefox' | 'webkit';
/** Whether this is the current baseline */
isActive: boolean;
/** ID of newer version if replaced */
replacedBy?: string;
}
// ============================================================================
// Component Props
// ============================================================================
/**
* Props for YouTubeVideoCard component
*/
export interface YouTubeVideoCardProps {
/** Thumbnail image URL or data URL */
thumbnailUrl: string;
/** Video title */
title: string;
/** Channel name */
channelTitle: string;
/** Formatted view count (e.g., "1.2M views") */
viewCount?: string;
/** Relative time string (e.g., "3 days ago") */
publishedAt?: string;
/** Whether this is user's uploaded thumbnail (for highlighting) */
isUserThumbnail?: boolean;
/** Layout variant */
variant?: PreviewMode;
/** Video duration string */
duration?: string;
/** Optional channel avatar URL */
channelAvatarUrl?: string;
}
/**
* Props for ThemeToggle component
*/
export interface ThemeToggleProps {
/** Current theme mode */
theme: ThemeMode;
/** Callback when theme changes */
onThemeChange: (theme: ThemeMode) => void;
}
/**
* Props for PreviewModeSelector component
*/
export interface PreviewModeSelectorProps {
/** Current preview mode */
mode: PreviewMode;
/** Callback when mode changes */
onModeChange: (mode: PreviewMode) => void;
}
// ============================================================================
// Validation
// ============================================================================
/**
* Thumbnail upload validation result
*/
export interface ThumbnailValidationResult {
valid: boolean;
errors: string[];
}
/**
* Validation constants
*/
export const VALIDATION_CONSTANTS = {
MAX_FILE_SIZE: 5 * 1024 * 1024, // 5MB
MAX_TITLE_LENGTH: 100,
MAX_CHANNEL_NAME_LENGTH: 50,
MAX_RECENT_THUMBNAILS: 10,
VISUAL_MATCH_THRESHOLD: 98, // percentage
SUPPORTED_FORMATS: ['image/jpeg', 'image/png', 'image/webp'] as const,
DURATION_REGEX: /^\d{1,2}:\d{2}(:\d{2})?$/,
} as const;