258 lines
5.4 KiB
Markdown
258 lines
5.4 KiB
Markdown
# Data Model: YouTube Design Preview Replica
|
|
|
|
**Date**: 2026-01-29
|
|
|
|
## Entities
|
|
|
|
### PreviewMode (Enum)
|
|
|
|
```typescript
|
|
type PreviewMode = 'desktop' | 'sidebar' | 'mobile';
|
|
```
|
|
|
|
| Value | Description | Thumbnail Size |
|
|
|-------|-------------|----------------|
|
|
| `desktop` | YouTube homepage/search grid layout | 360x202px |
|
|
| `sidebar` | YouTube related videos sidebar | 168x94px |
|
|
| `mobile` | YouTube mobile app full-width layout | 100% width |
|
|
|
|
### ThemeMode (Enum)
|
|
|
|
```typescript
|
|
type ThemeMode = 'light' | 'dark';
|
|
```
|
|
|
|
### VideoMetadata
|
|
|
|
User-customizable metadata for preview display.
|
|
|
|
```typescript
|
|
interface VideoMetadata {
|
|
title: string; // Video title (max 100 chars for display)
|
|
channelName: string; // Channel name
|
|
channelAvatarUrl?: string; // Optional channel avatar image URL
|
|
duration: string; // Format: "MM:SS" or "H:MM:SS"
|
|
viewCount: number; // Raw number, formatted for display
|
|
publishedAt: Date; // Used for "X days ago" calculation
|
|
}
|
|
```
|
|
|
|
**Validation Rules:**
|
|
- `title`: Required, 1-100 characters
|
|
- `channelName`: Required, 1-50 characters
|
|
- `duration`: Required, format `/^\d{1,2}:\d{2}(:\d{2})?$/`
|
|
- `viewCount`: Required, non-negative integer
|
|
- `publishedAt`: Required, valid Date, not in future
|
|
|
|
### UploadedThumbnail
|
|
|
|
Represents a user-uploaded thumbnail image.
|
|
|
|
```typescript
|
|
interface UploadedThumbnail {
|
|
id: string; // UUID
|
|
dataUrl: string; // Base64 data URL of the image
|
|
originalName: string; // Original filename
|
|
fileSize: number; // Size in bytes (max 5MB)
|
|
mimeType: string; // 'image/jpeg' | 'image/png' | 'image/webp'
|
|
width: number; // Original image width
|
|
height: number; // Original image height
|
|
uploadedAt: Date; // Timestamp
|
|
}
|
|
```
|
|
|
|
**Validation Rules:**
|
|
- `fileSize`: Max 5,242,880 bytes (5MB)
|
|
- `mimeType`: Only 'image/jpeg', 'image/png', 'image/webp'
|
|
- `width`, `height`: Positive integers
|
|
|
|
### PreviewState
|
|
|
|
Complete state for the preview tool, persisted to localStorage.
|
|
|
|
```typescript
|
|
interface PreviewState {
|
|
// Active preview configuration
|
|
currentThumbnail: UploadedThumbnail | null;
|
|
previewMode: PreviewMode;
|
|
themeMode: ThemeMode;
|
|
metadata: VideoMetadata;
|
|
|
|
// History for quick switching
|
|
recentThumbnails: UploadedThumbnail[]; // Max 10 items
|
|
|
|
// UI state
|
|
showMetadataEditor: boolean;
|
|
|
|
// Persistence metadata
|
|
lastSaved: Date;
|
|
version: number; // Schema version for migrations
|
|
}
|
|
```
|
|
|
|
### VisualTestResult
|
|
|
|
Result of Playwright visual comparison test.
|
|
|
|
```typescript
|
|
interface VisualTestResult {
|
|
testId: string;
|
|
previewMode: PreviewMode;
|
|
themeMode: ThemeMode;
|
|
capturedAt: Date;
|
|
|
|
// Comparison metrics
|
|
matchPercentage: number; // 0-100
|
|
passed: boolean; // matchPercentage >= threshold (98%)
|
|
|
|
// File references
|
|
actualScreenshotPath: string;
|
|
expectedScreenshotPath: string;
|
|
diffImagePath?: string; // Only if failed
|
|
|
|
// Error info
|
|
errorMessage?: string;
|
|
}
|
|
```
|
|
|
|
### ReferenceScreenshot
|
|
|
|
Stored YouTube reference for visual comparison.
|
|
|
|
```typescript
|
|
interface ReferenceScreenshot {
|
|
id: string;
|
|
previewMode: PreviewMode;
|
|
themeMode: ThemeMode;
|
|
capturedAt: Date;
|
|
capturedFrom: string; // YouTube URL
|
|
filePath: string;
|
|
|
|
// Metadata
|
|
viewportWidth: number;
|
|
viewportHeight: number;
|
|
browserType: string; // 'chromium' | 'firefox' | 'webkit'
|
|
|
|
// Version tracking
|
|
isActive: boolean; // Current baseline
|
|
replacedBy?: string; // ID of newer version
|
|
}
|
|
```
|
|
|
|
## Local Storage Schema
|
|
|
|
Key: `thumbpreview_state`
|
|
|
|
```typescript
|
|
interface LocalStorageSchema {
|
|
version: 1;
|
|
state: PreviewState;
|
|
}
|
|
```
|
|
|
|
**Storage Limits:**
|
|
- Total localStorage budget: ~5MB
|
|
- Each thumbnail (base64): ~1-2MB typical
|
|
- Keep max 10 recent thumbnails
|
|
- Automatic cleanup of oldest when limit reached
|
|
|
|
## State Transitions
|
|
|
|
### Thumbnail Upload Flow
|
|
|
|
```
|
|
[No Thumbnail]
|
|
|
|
|
v (user uploads file)
|
|
[Validating]
|
|
|
|
|
+-- (invalid) --> [Error: show message] --> [No Thumbnail]
|
|
|
|
|
v (valid)
|
|
[Processing]
|
|
|
|
|
v (resize/optimize if needed)
|
|
[Preview Ready]
|
|
|
|
|
v (auto-save to localStorage)
|
|
[Persisted]
|
|
```
|
|
|
|
### Preview Mode Switch
|
|
|
|
```
|
|
[Current Mode]
|
|
|
|
|
v (user selects new mode)
|
|
[Transitioning]
|
|
|
|
|
v (re-render with new layout)
|
|
[New Mode Active]
|
|
|
|
|
v (persist preference)
|
|
[Saved]
|
|
```
|
|
|
|
### Theme Mode Switch
|
|
|
|
```
|
|
[Current Theme]
|
|
|
|
|
v (user clicks toggle)
|
|
[Apply Theme Class]
|
|
|
|
|
v (CSS variables update)
|
|
[New Theme Active]
|
|
|
|
|
v (persist preference)
|
|
[Saved]
|
|
```
|
|
|
|
## Relationships
|
|
|
|
```
|
|
PreviewState
|
|
|
|
|
+-- 1:1 --> currentThumbnail (UploadedThumbnail)
|
|
|
|
|
+-- 1:N --> recentThumbnails (UploadedThumbnail[])
|
|
|
|
|
+-- 1:1 --> metadata (VideoMetadata)
|
|
|
|
|
+-- enum --> previewMode (PreviewMode)
|
|
|
|
|
+-- enum --> themeMode (ThemeMode)
|
|
|
|
VisualTestResult
|
|
|
|
|
+-- ref --> ReferenceScreenshot (expectedScreenshotPath)
|
|
|
|
|
+-- enum --> previewMode
|
|
|
|
|
+-- enum --> themeMode
|
|
```
|
|
|
|
## Default Values
|
|
|
|
```typescript
|
|
const DEFAULT_METADATA: VideoMetadata = {
|
|
title: 'Your Video Title Here',
|
|
channelName: 'Your Channel',
|
|
channelAvatarUrl: undefined,
|
|
duration: '10:30',
|
|
viewCount: 125000,
|
|
publishedAt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) // 1 week ago
|
|
};
|
|
|
|
const DEFAULT_STATE: PreviewState = {
|
|
currentThumbnail: null,
|
|
previewMode: 'desktop',
|
|
themeMode: 'light',
|
|
metadata: DEFAULT_METADATA,
|
|
recentThumbnails: [],
|
|
showMetadataEditor: false,
|
|
lastSaved: new Date(),
|
|
version: 1
|
|
};
|
|
```
|