mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-16 21:37:51 +00:00
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:
parent
7087a87ece
commit
02119a62d1
11
CHANGELOG.md
11
CHANGELOG.md
@ -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
30
src/lib/imageUtils.ts
Normal 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;
|
||||
}
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user