198 lines
4.2 KiB
Svelte
198 lines
4.2 KiB
Svelte
<script lang="ts">
|
|
import { Image, Upload, X } from 'lucide-svelte';
|
|
|
|
interface Props {
|
|
value?: string | null;
|
|
onChange?: (base64: string | null) => void;
|
|
maxSizeMB?: number;
|
|
maxWidth?: number;
|
|
maxHeight?: number;
|
|
}
|
|
|
|
let {
|
|
value = $bindable(null),
|
|
onChange,
|
|
maxSizeMB = 5,
|
|
maxWidth = 1200,
|
|
maxHeight = 1200
|
|
}: Props = $props();
|
|
|
|
let preview = $state<string | null>(value);
|
|
let error = $state<string | null>(null);
|
|
let inputElement: HTMLInputElement | null = null;
|
|
|
|
function handleFileSelect(event: Event) {
|
|
const target = event.target as HTMLInputElement;
|
|
const file = target.files?.[0];
|
|
if (!file) return;
|
|
|
|
error = null;
|
|
|
|
// Validar tamanho
|
|
if (file.size > maxSizeMB * 1024 * 1024) {
|
|
error = `Arquivo muito grande. Tamanho máximo: ${maxSizeMB}MB`;
|
|
return;
|
|
}
|
|
|
|
// Validar tipo
|
|
if (!file.type.startsWith('image/')) {
|
|
error = 'Por favor, selecione um arquivo de imagem';
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
const result = e.target?.result as string;
|
|
if (result) {
|
|
// Redimensionar imagem se necessário
|
|
resizeImage(result, maxWidth, maxHeight)
|
|
.then((resized) => {
|
|
preview = resized;
|
|
value = resized;
|
|
if (onChange) {
|
|
onChange(resized);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
error = err instanceof Error ? err.message : 'Erro ao processar imagem';
|
|
});
|
|
}
|
|
};
|
|
|
|
reader.onerror = () => {
|
|
error = 'Erro ao ler arquivo';
|
|
};
|
|
|
|
reader.readAsDataURL(file);
|
|
}
|
|
|
|
function resizeImage(
|
|
dataUrl: string,
|
|
maxWidth: number,
|
|
maxHeight: number
|
|
): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
let width = img.width;
|
|
let height = img.height;
|
|
|
|
// Calcular novas dimensões mantendo proporção
|
|
if (width > maxWidth || height > maxHeight) {
|
|
const ratio = Math.min(maxWidth / width, maxHeight / height);
|
|
width = width * ratio;
|
|
height = height * ratio;
|
|
}
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) {
|
|
reject(new Error('Não foi possível criar contexto do canvas'));
|
|
return;
|
|
}
|
|
|
|
ctx.drawImage(img, 0, 0, width, height);
|
|
const resizedDataUrl = canvas.toDataURL('image/jpeg', 0.85);
|
|
resolve(resizedDataUrl);
|
|
};
|
|
|
|
img.onerror = () => {
|
|
reject(new Error('Erro ao carregar imagem'));
|
|
};
|
|
|
|
img.src = dataUrl;
|
|
});
|
|
}
|
|
|
|
function removeImage() {
|
|
preview = null;
|
|
value = null;
|
|
if (inputElement) {
|
|
inputElement.value = '';
|
|
}
|
|
if (onChange) {
|
|
onChange(null);
|
|
}
|
|
}
|
|
|
|
function triggerFileInput() {
|
|
inputElement?.click();
|
|
}
|
|
|
|
$effect(() => {
|
|
preview = value;
|
|
});
|
|
</script>
|
|
|
|
<div class="image-upload">
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
class="hidden"
|
|
bind:this={inputElement}
|
|
onchange={handleFileSelect}
|
|
aria-label="Selecionar imagem do produto"
|
|
/>
|
|
|
|
{#if preview}
|
|
<div class="relative inline-block">
|
|
<img src={preview} alt="Preview da imagem do produto" class="max-w-full max-h-64 rounded-lg" />
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-circle btn-error absolute top-2 right-2"
|
|
onclick={removeImage}
|
|
aria-label="Remover imagem"
|
|
>
|
|
<X class="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="border-2 border-dashed border-base-300 rounded-lg p-8 text-center cursor-pointer hover:border-primary transition-colors"
|
|
onclick={triggerFileInput}
|
|
role="button"
|
|
tabindex="0"
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
triggerFileInput();
|
|
}
|
|
}}
|
|
>
|
|
<Upload class="h-12 w-12 mx-auto mb-4 text-base-content/40" />
|
|
<p class="text-base-content/70 font-medium mb-2">Clique para fazer upload da imagem</p>
|
|
<p class="text-sm text-base-content/50">
|
|
PNG, JPG ou GIF até {maxSizeMB}MB
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if error}
|
|
<div class="alert alert-error mt-4">
|
|
<span>{error}</span>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if preview}
|
|
<button
|
|
type="button"
|
|
class="btn btn-sm btn-outline btn-primary mt-4"
|
|
onclick={triggerFileInput}
|
|
>
|
|
<Image class="h-4 w-4" />
|
|
Alterar Imagem
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.image-upload {
|
|
width: 100%;
|
|
}
|
|
</style>
|
|
|