feat: implementa otimização global de imagens

- Adiciona função utilitária para otimização de imagens
- Converte automaticamente para WebP
- Implementa redimensionamento contextual
- Centraliza lógica de transformação
- Melhora performance de carregamento
This commit is contained in:
Lucas Santana 2024-12-23 18:42:53 -03:00
parent 7087a87ece
commit 02119a62d1
5 changed files with 61 additions and 6 deletions

View File

@ -39,6 +39,15 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Melhor performance em navegação
### Modificado
- Otimização global de imagens
- Conversão automática para WebP
- Redimensionamento otimizado por contexto
- Parâmetros de qualidade personalizados
- Função utilitária centralizada
- Implementação em todas as rotas
- Otimização contextual por uso
- Pré-carregamento otimizado
- Otimização de imagens de capa
- Uso da primeira página como capa
- Tamanho reduzido para thumbnails
@ -69,7 +78,7 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Melhor tratamento de estados de loading e erro
- Implementação de componente ImageWithLoading
- Sistema de cache de imagens
- Otimização de URLs de imagem
- Otimizaç<EFBFBD><EFBFBD>o de URLs de imagem
- Refatoração de componentes para melhor reuso
- Separação de lógica de carregamento de imagens

30
src/lib/imageUtils.ts Normal file
View File

@ -0,0 +1,30 @@
interface ImageOptions {
width?: number;
height?: number;
quality?: number;
}
export function getOptimizedImageUrl(url: string, options: ImageOptions = {}): string {
const {
width = 800,
height = undefined,
quality = 80
} = options;
// Se for URL do Supabase Storage
if (url.includes('storage.googleapis.com')) {
const params = new URLSearchParams({
width: width.toString(),
quality: quality.toString(),
format: 'webp'
});
if (height) {
params.append('height', height.toString());
}
return `${url}?${params.toString()}`;
}
return url;
}

View File

@ -6,6 +6,7 @@ import { AudioRecorder } from '../../components/story/AudioRecorder';
import type { Story } from '../../types/database';
import { StoryMetrics } from '../../components/story/StoryMetrics';
import type { MetricsData } from '../../components/story/StoryMetrics';
import { getOptimizedImageUrl } from '../../lib/imageUtils';
interface StoryRecording {
id: string;
@ -304,7 +305,10 @@ export function StoryPage() {
const nextImageUrl = story?.content?.pages?.[currentPage + 1]?.image;
if (nextImageUrl) {
const nextImage = new Image();
nextImage.src = nextImageUrl;
nextImage.src = getOptimizedImageUrl(nextImageUrl, {
width: 1200,
quality: 85
});
}
}, [currentPage, story]);
@ -398,7 +402,10 @@ export function StoryPage() {
{/* Imagem da página atual */}
{story?.content?.pages?.[currentPage]?.image && (
<ImageWithLoading
src={story.content.pages[currentPage].image}
src={getOptimizedImageUrl(story.content.pages[currentPage].image, {
width: 1200,
quality: 85
})}
alt={`Página ${currentPage + 1}`}
className="w-full h-full object-cover"
/>

View File

@ -3,6 +3,7 @@ import { Plus, BookOpen, Clock, TrendingUp, Award } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { supabase } from '../../lib/supabase';
import type { Story, Student } from '../../types/database';
import { getOptimizedImageUrl } from '../../lib/imageUtils';
interface DashboardMetrics {
totalStories: number;
@ -252,7 +253,10 @@ export function StudentDashboardPage() {
{story.cover && (
<div className="relative aspect-video">
<img
src={`${story.cover.image_url}?width=400&quality=80&format=webp`}
src={getOptimizedImageUrl(story.cover.image_url, {
width: 400,
height: 300
})}
alt={story.title}
className="w-full h-48 object-cover"
loading="lazy"

View File

@ -3,6 +3,7 @@ import { Plus, Search, Filter, BookOpen, ArrowUpDown } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { supabase } from '../../lib/supabase';
import type { Story } from '../../types/database';
import { getOptimizedImageUrl } from '../../lib/imageUtils';
type StoryStatus = 'all' | 'draft' | 'published';
type SortOption = 'recent' | 'oldest' | 'title' | 'performance';
@ -197,10 +198,14 @@ export function StudentStoriesPage() {
className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden cursor-pointer hover:shadow-md transition"
onClick={() => navigate(`/aluno/historias/${story.id}`)}
>
{story.content?.pages?.[0]?.image && (
{story.cover && (
<div className="relative aspect-video">
<img
src={story.content.pages[0].image}
src={getOptimizedImageUrl(story.cover.image_url, {
width: 400,
height: 300,
quality: 80
})}
alt={story.title}
className="w-full h-48 object-cover"
loading="lazy"