| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- // Image URL validation utilities for the ImageInput component
- import { apiClient } from './api';
- export interface ImageValidationResult {
- isValid: boolean;
- error?: string;
- detectedType?: string;
- filename?: string;
- isCorsBlocked?: boolean;
- base64Data?: string;
- }
- export interface ImageInputMode {
- type: 'file' | 'url' | null;
- value?: File | string;
- }
- // Supported image MIME types and extensions
- export const IMAGE_MIME_TYPES = [
- 'image/jpeg',
- 'image/jpg',
- 'image/png',
- 'image/gif',
- 'image/webp',
- 'image/bmp',
- 'image/svg+xml',
- 'image/tiff'
- ] as const;
- export const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg', 'tiff'] as const;
- export type ImageExtension = typeof IMAGE_EXTENSIONS[number];
- // Extract extension from filename or URL
- export function extractExtension(filenameOrUrl: string): string | null {
- // Remove query parameters and fragments
- const cleanUrl = filenameOrUrl.split('?')[0].split('#')[0];
- // Get the last part after dot
- const extension = cleanUrl.split('.').pop()?.toLowerCase();
- return extension || null;
- }
- // Check if URL contains image extension (more flexible than just checking the end)
- export function hasImageExtension(url: string): boolean {
- const extension = extractExtension(url);
- return extension ? isValidImageExtension(extension) : false;
- }
- // Check if extension is a valid image extension
- export function isValidImageExtension(extension: string): extension is ImageExtension {
- return IMAGE_EXTENSIONS.includes(extension as ImageExtension);
- }
- // Get MIME type from extension
- export function getMimeTypeFromExtension(extension: string): string {
- const extToMime: Record<ImageExtension, string> = {
- 'jpg': 'image/jpeg',
- 'jpeg': 'image/jpeg',
- 'png': 'image/png',
- 'gif': 'image/gif',
- 'webp': 'image/webp',
- 'bmp': 'image/bmp',
- 'svg': 'image/svg+xml',
- 'tiff': 'image/tiff'
- };
- return extToMime[extension as ImageExtension] || 'image/unknown';
- }
- // Validate file object
- export function validateFile(file: File): ImageValidationResult {
- // Check if file is empty
- if (!file || file.size === 0) {
- return {
- isValid: false,
- error: 'File is empty or invalid'
- };
- }
- // Check file size (max 10MB)
- const maxSize = 10 * 1024 * 1024; // 10MB
- if (file.size > maxSize) {
- return {
- isValid: false,
- error: `File size (${(file.size / 1024 / 1024).toFixed(1)}MB) exceeds 10MB limit`
- };
- }
- // Check MIME type
- if (!IMAGE_MIME_TYPES.includes(file.type as any)) {
- return {
- isValid: false,
- error: `File type '${file.type}' is not supported. Supported formats: ${IMAGE_EXTENSIONS.join(', ')}`
- };
- }
- return {
- isValid: true,
- detectedType: file.type,
- filename: file.name
- };
- }
- // Basic URL validation without network requests
- export function validateUrlFormat(url: string): ImageValidationResult {
- try {
- new URL(url);
- } catch {
- return {
- isValid: false,
- error: 'Invalid URL format'
- };
- }
- // Check if it has a valid image extension
- if (!hasImageExtension(url)) {
- return {
- isValid: false,
- error: 'URL does not point to a supported image format. Supported formats: ' + IMAGE_EXTENSIONS.join(', ')
- };
- }
- // Basic validation passed - return the extension-based result
- const extension = extractExtension(url);
- return {
- isValid: true,
- detectedType: extension ? getMimeTypeFromExtension(extension) : undefined,
- filename: url.split('/').pop() || url
- };
- }
- // Validate image URL using server-side download endpoint
- export async function validateImageUrlWithBase64(url: string): Promise<ImageValidationResult> {
- // First validate URL format and extension
- const formatValidation = validateUrlFormat(url);
- if (!formatValidation.isValid) {
- return formatValidation;
- }
- try {
- // Use server-side download endpoint to avoid CORS issues
- const result = await apiClient.downloadImageFromUrl(url);
- return {
- isValid: true,
- detectedType: result.mimeType,
- filename: result.filename,
- base64Data: result.base64Data
- };
- } catch (error) {
- return {
- isValid: false,
- error: `Failed to download image from URL: ${error instanceof Error ? error.message : 'Unknown error'}`
- };
- }
- }
- // Validate image input (file or URL) with base64 conversion
- export async function validateImageInput(
- input: File | string
- ): Promise<ImageValidationResult> {
- if (input instanceof File) {
- return validateFile(input);
- } else {
- return await validateImageUrlWithBase64(input);
- }
- }
- // Get display name for image input
- export function getImageDisplayName(input: File | string): string {
- if (input instanceof File) {
- return input.name;
- } else {
- const url = new URL(input);
- return url.pathname.split('/').pop() || input;
- }
- }
- // Convert file to data URL for preview
- export function fileToDataURL(file: File): Promise<string> {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = () => resolve(reader.result as string);
- reader.onerror = reject;
- reader.readAsDataURL(file);
- });
- }
|