aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
690 lines (687 loc) • 22.8 kB
JavaScript
'use client';
import { jsx } from 'react/jsx-runtime';
import { useState, useRef, useCallback, createContext, useContext } from 'react';
const ImageProcessingContext = /*#__PURE__*/createContext(null);
// Default templates for common use cases
const defaultTemplates = [
// Social Media
{
id: 'instagram_post',
name: 'Instagram Post',
category: 'social',
width: 1080,
height: 1080,
description: 'Square format perfect for Instagram posts',
thumbnail: 'https://via.placeholder.com/100x100/e91e63/ffffff?text=IG',
preset: {
saturation: 10,
contrast: 5
}
}, {
id: 'instagram_story',
name: 'Instagram Story',
category: 'social',
width: 1080,
height: 1920,
description: 'Vertical format for Instagram and Facebook stories',
thumbnail: 'https://via.placeholder.com/56x100/e91e63/ffffff?text=Story'
}, {
id: 'facebook_cover',
name: 'Facebook Cover',
category: 'social',
width: 1200,
height: 630,
description: 'Facebook cover photo dimensions',
thumbnail: 'https://via.placeholder.com/100x52/1877f2/ffffff?text=FB'
}, {
id: 'twitter_header',
name: 'Twitter Header',
category: 'social',
width: 1500,
height: 500,
description: 'Twitter profile header image',
thumbnail: 'https://via.placeholder.com/100x33/1da1f2/ffffff?text=TW'
},
// Web
{
id: 'blog_banner',
name: 'Blog Banner',
category: 'web',
width: 1200,
height: 400,
description: 'Wide banner for blog posts and articles',
thumbnail: 'https://via.placeholder.com/100x33/6c757d/ffffff?text=Blog'
}, {
id: 'web_thumbnail',
name: 'Web Thumbnail',
category: 'web',
width: 400,
height: 300,
description: 'Standard web thumbnail size',
thumbnail: 'https://via.placeholder.com/100x75/17a2b8/ffffff?text=Thumb'
},
// Print
{
id: 'business_card',
name: 'Business Card',
category: 'print',
width: 1050,
height: 600,
description: 'Standard business card size (3.5" x 2")',
thumbnail: 'https://via.placeholder.com/100x57/28a745/ffffff?text=Card'
}, {
id: 'poster_a4',
name: 'A4 Poster',
category: 'print',
width: 2480,
height: 3508,
description: 'A4 poster size at 300 DPI',
thumbnail: 'https://via.placeholder.com/71x100/ffc107/ffffff?text=A4'
}];
// Mock AI image processing functions
const mockImageProcessor = {
async analyzeImage(imageUrl) {
// Simulate AI analysis
await new Promise(resolve => setTimeout(resolve, 1000));
return {
dominantColors: ['var(--glass-color-primary)', '#1e40af', '#93c5fd'],
brightness: 65 + Math.random() * 30,
contrast: 50 + Math.random() * 30,
saturation: 40 + Math.random() * 40,
sharpness: 60 + Math.random() * 30
};
},
async detectFaces(imageUrl) {
await new Promise(resolve => setTimeout(resolve, 1500));
// Mock face detection - return 0-3 random faces
const faceCount = Math.floor(Math.random() * 4);
const faces = [];
for (let i = 0; i < faceCount; i++) {
faces.push({
id: `face_${i}`,
x: Math.random() * 0.7,
y: Math.random() * 0.7,
width: 0.1 + Math.random() * 0.2,
height: 0.1 + Math.random() * 0.2,
confidence: 0.7 + Math.random() * 0.3,
landmarks: {
leftEye: {
x: Math.random(),
y: Math.random()
},
rightEye: {
x: Math.random(),
y: Math.random()
},
nose: {
x: Math.random(),
y: Math.random()
},
mouth: {
x: Math.random(),
y: Math.random()
}
}
});
}
return faces;
},
async optimizeImage(imageUrl, options) {
await new Promise(resolve => setTimeout(resolve, 2000));
// In a real implementation, this would call an AI service
// For demo, we'll return the same URL with a query parameter
return `${imageUrl}?optimized=true&q=${options.quality || 85}`;
}
};
const ImageProcessingProvider = ({
children
}) => {
const [images, setImages] = useState([]);
const [uploadProgresses, setUploadProgresses] = useState([]);
const [templates] = useState(defaultTemplates);
const [defaultOptimizations, setDefaultOptimizations] = useState({
quality: 85,
format: 'auto',
removeMetadata: true,
progressive: true
});
const [autoOptimize, setAutoOptimize] = useState(true);
useRef();
const generateId = useCallback(() => {
return `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}, []);
const createImageFromFile = useCallback(async file => {
return new Promise((resolve, reject) => {
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
img.onload = async () => {
canvas.width = img.width;
canvas.height = img.height;
ctx?.drawImage(img, 0, 0);
const url = URL.createObjectURL(file);
const metadata = await mockImageProcessor.analyzeImage(url);
const imageFile = {
id: generateId(),
file,
name: file.name,
originalName: file.name,
size: file.size,
type: file.type,
width: img.width,
height: img.height,
aspectRatio: img.width / img.height,
url,
uploadedAt: new Date(),
editHistory: [],
metadata: {
format: file.type.split('/')[1],
quality: 100,
colorSpace: 'sRGB',
hasAlpha: file.type.includes('png'),
fileSize: file.size,
dominantColors: ['var(--glass-black)'],
brightness: 50,
contrast: 50,
saturation: 50,
sharpness: 50,
...metadata
},
tags: []
};
resolve(imageFile);
};
img.onerror = () => reject(new Error('Failed to load image'));
img.src = URL.createObjectURL(file);
});
}, [generateId]);
const addImage = useCallback(async file => {
const imageFile = await createImageFromFile(file);
setImages(prev => [...prev, imageFile]);
// Auto-optimize if enabled
if (autoOptimize) {
setTimeout(() => {
optimizeImage(imageFile.id, defaultOptimizations);
}, 100);
}
return imageFile;
}, [createImageFromFile, autoOptimize, defaultOptimizations]);
const addImages = useCallback(async files => {
const imageFiles = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.type.startsWith('image/')) {
try {
const imageFile = await createImageFromFile(file);
imageFiles.push(imageFile);
// Update progress
setUploadProgresses(prev => [...prev, {
imageId: imageFile.id,
progress: 100,
status: 'completed'
}]);
} catch (error) {
console.error('Failed to process image:', file.name, error);
}
}
}
setImages(prev => [...prev, ...imageFiles]);
return imageFiles;
}, [createImageFromFile]);
const removeImage = useCallback(id => {
setImages(prev => {
const image = prev.find(img => img.id === id);
if (image) {
URL.revokeObjectURL(image.url);
if (image.thumbnailUrl) {
URL.revokeObjectURL(image.thumbnailUrl);
}
}
return prev.filter(img => img.id !== id);
});
setUploadProgresses(prev => prev.filter(p => p.imageId !== id));
}, []);
const getImage = useCallback(id => {
return images.find(img => img.id === id);
}, [images]);
const updateImage = useCallback((id, updates) => {
setImages(prev => prev.map(img => img.id === id ? {
...img,
...updates
} : img));
}, []);
const optimizeImage = useCallback(async (imageId, options = {}) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
// Update progress
setUploadProgresses(prev => [...prev, {
imageId,
progress: 0,
status: 'processing',
message: 'Analyzing image...'
}]);
const optimizationOptions = {
...defaultOptimizations,
...options
};
try {
// Simulate optimization progress
for (let progress = 10; progress <= 90; progress += 20) {
await new Promise(resolve => setTimeout(resolve, 200));
setUploadProgresses(prev => prev.map(p => p.imageId === imageId ? {
...p,
progress
} : p));
}
const optimizedUrl = await mockImageProcessor.optimizeImage(image.url, optimizationOptions);
const optimizedImage = {
...image,
url: optimizedUrl,
optimizedAt: new Date(),
metadata: {
...image.metadata,
quality: optimizationOptions.quality || 85,
compressionRatio: 0.3 + Math.random() * 0.4
},
editHistory: [...image.editHistory, {
id: generateId(),
type: 'color',
timestamp: new Date(),
parameters: optimizationOptions
}]
};
setImages(prev => prev.map(img => img.id === imageId ? optimizedImage : img));
setUploadProgresses(prev => prev.map(p => p.imageId === imageId ? {
...p,
progress: 100,
status: 'completed',
message: 'Optimization complete!'
} : p));
return optimizedImage;
} catch (error) {
setUploadProgresses(prev => prev.map(p => p.imageId === imageId ? {
...p,
status: 'error',
message: 'Optimization failed'
} : p));
throw error;
}
}, [getImage, defaultOptimizations, generateId]);
const batchOptimize = useCallback(async (imageIds, options = {}) => {
const results = [];
for (const imageId of imageIds) {
try {
const optimized = await optimizeImage(imageId, options);
results.push(optimized);
} catch (error) {
console.error(`Failed to optimize image ${imageId}:`, error);
}
}
return results;
}, [optimizeImage]);
const detectFaces = useCallback(async imageId => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
const faces = await mockImageProcessor.detectFaces(image.url);
updateImage(imageId, {
faces
});
return faces;
}, [getImage, updateImage]);
const applyFilter = useCallback(async (imageId, filters) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
// Simulate filter processing
await new Promise(resolve => setTimeout(resolve, 1000));
const editOperation = {
id: generateId(),
type: 'filter',
timestamp: new Date(),
parameters: filters
};
const updatedImage = {
...image,
editHistory: [...image.editHistory, editOperation],
metadata: {
...image.metadata,
brightness: image.metadata.brightness + (filters.brightness || 0),
contrast: image.metadata.contrast + (filters.contrast || 0),
saturation: image.metadata.saturation + (filters.saturation || 0)
}
};
setImages(prev => prev.map(img => img.id === imageId ? updatedImage : img));
return updatedImage;
}, [getImage, generateId]);
const cropImage = useCallback(async (imageId, cropArea) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
// Simulate crop processing
await new Promise(resolve => setTimeout(resolve, 800));
const editOperation = {
id: generateId(),
type: 'crop',
timestamp: new Date(),
parameters: cropArea
};
const croppedImage = {
...image,
width: Math.round(image.width * cropArea.width),
height: Math.round(image.height * cropArea.height),
aspectRatio: cropArea.width / cropArea.height,
editHistory: [...image.editHistory, editOperation]
};
setImages(prev => prev.map(img => img.id === imageId ? croppedImage : img));
return croppedImage;
}, [getImage, generateId]);
const resizeImage = useCallback(async (imageId, width, height, maintainAspect = true) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
let newWidth = width;
let newHeight = height;
if (maintainAspect) {
const aspectRatio = image.width / image.height;
if (width / height > aspectRatio) {
newWidth = height * aspectRatio;
} else {
newHeight = width / aspectRatio;
}
}
await new Promise(resolve => setTimeout(resolve, 600));
const editOperation = {
id: generateId(),
type: 'resize',
timestamp: new Date(),
parameters: {
width: newWidth,
height: newHeight,
maintainAspect
}
};
const resizedImage = {
...image,
width: Math.round(newWidth),
height: Math.round(newHeight),
aspectRatio: newWidth / newHeight,
editHistory: [...image.editHistory, editOperation]
};
setImages(prev => prev.map(img => img.id === imageId ? resizedImage : img));
return resizedImage;
}, [getImage, generateId]);
const addWatermark = useCallback(async (imageId, watermark) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
await new Promise(resolve => setTimeout(resolve, 1200));
const editOperation = {
id: generateId(),
type: 'watermark',
timestamp: new Date(),
parameters: watermark
};
const watermarkedImage = {
...image,
editHistory: [...image.editHistory, editOperation]
};
setImages(prev => prev.map(img => img.id === imageId ? watermarkedImage : img));
return watermarkedImage;
}, [getImage, generateId]);
const removeBackground = useCallback(async imageId => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
await new Promise(resolve => setTimeout(resolve, 3000));
const editOperation = {
id: generateId(),
type: 'background',
timestamp: new Date(),
parameters: {
action: 'remove'
}
};
const processedImage = {
...image,
metadata: {
...image.metadata,
hasAlpha: true
},
editHistory: [...image.editHistory, editOperation]
};
setImages(prev => prev.map(img => img.id === imageId ? processedImage : img));
return processedImage;
}, [getImage, generateId]);
const smartCrop = useCallback(async (imageId, aspectRatio) => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
// If faces are detected, use them for smart cropping
let faces = image.faces;
if (!faces || faces.length === 0) {
faces = await detectFaces(imageId);
}
let cropArea;
if (faces.length > 0) {
// Center crop around faces
const facesCenterX = faces.reduce((sum, face) => sum + face.x + face.width / 2, 0) / faces.length;
const facesCenterY = faces.reduce((sum, face) => sum + face.y + face.height / 2, 0) / faces.length;
const currentAspect = image.width / image.height;
if (aspectRatio > currentAspect) {
// Crop height
const newHeight = image.width / aspectRatio;
cropArea = {
x: 0,
y: Math.max(0, facesCenterY - newHeight / 2),
width: 1,
height: newHeight / image.height
};
} else {
// Crop width
const newWidth = image.height * aspectRatio;
cropArea = {
x: Math.max(0, facesCenterX - newWidth / 2),
y: 0,
width: newWidth / image.width,
height: 1
};
}
} else {
// Center crop
const currentAspect = image.width / image.height;
if (aspectRatio > currentAspect) {
const newHeight = image.width / aspectRatio;
cropArea = {
x: 0,
y: (image.height - newHeight) / 2 / image.height,
width: 1,
height: newHeight / image.height
};
} else {
const newWidth = image.height * aspectRatio;
cropArea = {
x: (image.width - newWidth) / 2 / image.width,
y: 0,
width: newWidth / image.width,
height: 1
};
}
}
return cropImage(imageId, cropArea);
}, [getImage, detectFaces, cropImage]);
const enhanceImage = useCallback(async imageId => {
const image = getImage(imageId);
if (!image) throw new Error('Image not found');
// Apply AI enhancement (brightness, contrast, sharpness optimization)
const enhancementFilters = {
brightness: Math.max(-20, Math.min(20, 10 - image.metadata.brightness * 0.2)),
contrast: Math.max(-20, Math.min(20, 15 - image.metadata.contrast * 0.3)),
sharpen: Math.max(0, Math.min(10, 8 - image.metadata.sharpness * 0.1)),
saturation: Math.max(-15, Math.min(15, 5 - image.metadata.saturation * 0.15))
};
return applyFilter(imageId, enhancementFilters);
}, [getImage, applyFilter]);
const getOptimizationStats = useCallback(() => {
const processedImages = images.filter(img => img.metadata.compressionRatio);
const totalOriginalSize = processedImages.reduce((sum, img) => sum + img.size, 0);
const totalOptimizedSize = processedImages.reduce((sum, img) => sum + img.size * (1 - (img.metadata.compressionRatio || 0)), 0);
return {
totalSaved: Math.round((totalOriginalSize - totalOptimizedSize) / 1024),
// KB
averageReduction: processedImages.length > 0 ? Math.round(processedImages.reduce((sum, img) => sum + (img.metadata.compressionRatio || 0), 0) / processedImages.length * 100) : 0,
imagesProcessed: processedImages.length,
mostUsedFormat: 'JPEG' // Mock data
};
}, [images]);
const clearProgress = useCallback(imageId => {
setUploadProgresses(prev => prev.filter(p => p.imageId !== imageId));
}, []);
// Placeholder implementations for other methods
const rotateImage = useCallback(async (imageId, degrees) => {
// Implementation would go here
return getImage(imageId);
}, [getImage]);
const replaceBackground = useCallback(async (imageId, backgroundImage) => {
// Implementation would go here
return getImage(imageId);
}, [getImage]);
const upscaleImage = useCallback(async (imageId, factor) => {
// Implementation would go here
return getImage(imageId);
}, [getImage]);
const batchResize = useCallback(async (imageIds, width, height) => {
const results = [];
for (const imageId of imageIds) {
try {
const resized = await resizeImage(imageId, width, height);
results.push(resized);
} catch (error) {
console.error(`Failed to resize image ${imageId}:`, error);
}
}
return results;
}, [resizeImage]);
const batchFilter = useCallback(async (imageIds, filters) => {
const results = [];
for (const imageId of imageIds) {
try {
const filtered = await applyFilter(imageId, filters);
results.push(filtered);
} catch (error) {
console.error(`Failed to filter image ${imageId}:`, error);
}
}
return results;
}, [applyFilter]);
const batchWatermark = useCallback(async (imageIds, watermark) => {
const results = [];
for (const imageId of imageIds) {
try {
const watermarked = await addWatermark(imageId, watermark);
results.push(watermarked);
} catch (error) {
console.error(`Failed to watermark image ${imageId}:`, error);
}
}
return results;
}, [addWatermark]);
const uploadToCloud = useCallback(async (imageId, provider = 'cloudinary') => {
// Mock cloud upload
await new Promise(resolve => setTimeout(resolve, 2000));
const baseUrl = `https://${provider}.example.com`;
return {
original: `${baseUrl}/original/${imageId}`,
optimized: `${baseUrl}/optimized/${imageId}`,
thumbnail: `${baseUrl}/thumb/${imageId}`,
responsive: {
small: `${baseUrl}/w_400/${imageId}`,
medium: `${baseUrl}/w_800/${imageId}`,
large: `${baseUrl}/w_1200/${imageId}`,
xlarge: `${baseUrl}/w_1600/${imageId}`
}
};
}, []);
const generateResponsiveImages = useCallback(async (imageId, sizes) => {
return uploadToCloud(imageId);
}, [uploadToCloud]);
const applyTemplate = useCallback(async (imageId, templateId) => {
const template = templates.find(t => t.id === templateId);
if (!template) throw new Error('Template not found');
let result = await resizeImage(imageId, template.width, template.height, false);
if (template.preset) {
result = await applyFilter(imageId, template.preset);
}
return result;
}, [templates, resizeImage, applyFilter]);
const createCustomTemplate = useCallback(template => {
const newTemplate = {
...template,
id: generateId()
};
// In a real app, you'd save this to state or backend
return newTemplate;
}, [generateId]);
const getImageInsights = useCallback(imageId => {
const image = getImage(imageId);
if (!image) return {
colorPalette: [],
dominantColor: 'var(--glass-black)',
brightness: 0,
complexity: 0,
recommendedFormats: []
};
return {
colorPalette: image.metadata.dominantColors,
dominantColor: image.metadata.dominantColors[0] || 'var(--glass-black)',
brightness: image.metadata.brightness,
complexity: Math.random() * 100,
// Mock complexity score
recommendedFormats: image.metadata.hasAlpha ? ['PNG', 'WebP'] : ['JPEG', 'WebP', 'AVIF']
};
}, [getImage]);
const value = {
images,
addImage,
addImages,
removeImage,
getImage,
updateImage,
optimizeImage,
batchOptimize,
cropImage,
resizeImage,
applyFilter,
rotateImage,
addWatermark,
detectFaces,
removeBackground,
replaceBackground,
smartCrop,
enhanceImage,
upscaleImage,
batchResize,
batchFilter,
batchWatermark,
uploadToCloud,
generateResponsiveImages,
templates,
applyTemplate,
createCustomTemplate,
getOptimizationStats,
getImageInsights,
uploadProgresses,
clearProgress,
defaultOptimizations,
setDefaultOptimizations,
autoOptimize,
setAutoOptimize
};
return jsx(ImageProcessingContext.Provider, {
"data-glass-component": true,
value: value,
children: children
});
};
const useImageProcessing = () => {
const context = useContext(ImageProcessingContext);
if (!context) {
throw new Error('useImageProcessing must be used within an ImageProcessingProvider');
}
return context;
};
export { ImageProcessingProvider as GlassImageProcessingProvider, ImageProcessingProvider, useImageProcessing };
//# sourceMappingURL=GlassImageProcessingProvider.js.map