All checks were successful
Deploy ASP.NET MVC to OCI / build-and-deploy (push) Successful in 9m32s
450 lines
20 KiB
Plaintext
450 lines
20 KiB
Plaintext
@{
|
|
ViewData["Title"] = ViewBag.PageTitle;
|
|
}
|
|
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="text-center mb-4">
|
|
<h1 class="display-5">@ViewBag.PageTitle</h1>
|
|
<p class="lead text-muted">@ViewBag.PageDescription</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row justify-content-center">
|
|
<div class="col-lg-10 col-xl-8">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-file-earmark-image me-2"></i>Conversor de Imagens
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
@if (ViewBag.ConversionError != null)
|
|
{
|
|
<div class="alert alert-danger" role="alert">
|
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
|
@ViewBag.ConversionError
|
|
</div>
|
|
}
|
|
|
|
<!-- Navigation Tabs -->
|
|
<ul class="nav nav-tabs mb-4" id="converterTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="jpg-to-webp-tab" data-bs-toggle="tab" data-bs-target="#jpg-to-webp" type="button" role="tab" aria-controls="jpg-to-webp" aria-selected="true">
|
|
<i class="bi bi-arrow-right me-2"></i>@ViewBag.JpgToWebpTabTitle
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="webp-to-jpg-tab" data-bs-toggle="tab" data-bs-target="#webp-to-jpg" type="button" role="tab" aria-controls="webp-to-jpg" aria-selected="false">
|
|
<i class="bi bi-arrow-left me-2"></i>@ViewBag.WebpToJpgTabTitle
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content" id="converterTabContent">
|
|
<!-- JPG to WebP Tab -->
|
|
<div class="tab-pane fade show active" id="jpg-to-webp" role="tabpanel" aria-labelledby="jpg-to-webp-tab">
|
|
<form id="jpgToWebpForm" asp-action="ConvertJpgToWebp" method="post" enctype="multipart/form-data" class="needs-validation">
|
|
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
|
|
|
<div class="mb-4">
|
|
<label for="jpgFile" class="form-label fw-semibold">@ViewBag.JpgFileInputLabel</label>
|
|
<input class="form-control" type="file" id="jpgFile" name="jpgFile" accept=".jpg,.jpeg,.JPG,.JPEG" required>
|
|
<div class="form-text">
|
|
<i class="bi bi-info-circle me-1"></i>Formatos aceitos: .jpg, .jpeg (máx. 10MB para preview)
|
|
</div>
|
|
<div class="invalid-feedback">
|
|
Por favor, selecione um arquivo JPG válido.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="button" id="convertJpgPreviewBtn" class="btn btn-primary">
|
|
<i class="bi bi-eye me-2"></i>Converter e Visualizar
|
|
</button>
|
|
<button type="submit" class="btn btn-outline-secondary">
|
|
<i class="bi bi-download me-2"></i>Converter e Baixar Diretamente
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- WebP to JPG Tab -->
|
|
<div class="tab-pane fade" id="webp-to-jpg" role="tabpanel" aria-labelledby="webp-to-jpg-tab">
|
|
<form id="webpToJpgForm" asp-action="ConvertWebpToJpg" method="post" enctype="multipart/form-data" class="needs-validation">
|
|
<div asp-validation-summary="ModelOnly" class="alert alert-danger" role="alert"></div>
|
|
|
|
<div class="mb-4">
|
|
<label for="webpFile" class="form-label fw-semibold">@ViewBag.WebpFileInputLabel</label>
|
|
<input class="form-control" type="file" id="webpFile" name="webpFile" accept=".webp,.WEBP" required>
|
|
<div class="form-text">
|
|
<i class="bi bi-info-circle me-1"></i>Formatos aceitos: .webp (máx. 10MB para preview)
|
|
</div>
|
|
<div class="invalid-feedback">
|
|
Por favor, selecione um arquivo WebP válido.
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button type="button" id="convertWebpPreviewBtn" class="btn btn-primary">
|
|
<i class="bi bi-eye me-2"></i>Converter e Visualizar
|
|
</button>
|
|
<button type="submit" class="btn btn-outline-secondary">
|
|
<i class="bi bi-download me-2"></i>Converter e Baixar Diretamente
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Spinner -->
|
|
<div id="loadingSpinner" class="text-center mt-4" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Convertendo...</span>
|
|
</div>
|
|
<p class="mt-2 text-muted">Convertendo sua imagem...</p>
|
|
</div>
|
|
|
|
<!-- Preview Area -->
|
|
<div id="previewArea" class="mt-4" style="display: none;">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h6 class="mb-0">
|
|
<i class="bi bi-image me-2"></i>Imagem Convertida
|
|
</h6>
|
|
<button id="downloadBtn" class="btn btn-success btn-sm">
|
|
<i class="bi bi-download me-1"></i>Baixar
|
|
</button>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
<img id="previewImage" src="" alt="Imagem convertida" class="img-fluid rounded" style="max-height: 400px; max-width: 100%;">
|
|
<div class="mt-2">
|
|
<small id="imageInfo" class="text-muted"></small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Error Area -->
|
|
<div id="errorArea" class="mt-4" style="display: none;">
|
|
<div class="alert alert-warning" role="alert">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
<span id="errorMessage"></span>
|
|
<button id="fallbackDownloadBtn" class="btn btn-outline-primary btn-sm ms-2">
|
|
Tentar Download Direto
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FAQ Accordion -->
|
|
<div class="row mt-5">
|
|
<div class="col-lg-10 mx-auto">
|
|
<div class="converter-faq">
|
|
<h3 class="h4 mb-3 text-center">Perguntas Frequentes</h3>
|
|
<div class="accordion" id="jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingWhatJpgWebp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhatJpgWebp" aria-expanded="false" aria-controls="collapseWhatJpgWebp">
|
|
<i class="bi bi-question-circle me-2"></i>@ViewBag.FaqWhatTitle
|
|
</button>
|
|
</h2>
|
|
<div id="collapseWhatJpgWebp" class="accordion-collapse collapse" aria-labelledby="headingWhatJpgWebp" data-bs-parent="#jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-body">
|
|
@ViewBag.FaqWhatContent
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingHowJpgWebp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseHowJpgWebp" aria-expanded="false" aria-controls="collapseHowJpgWebp">
|
|
<i class="bi bi-gear me-2"></i>@ViewBag.FaqHowTitle
|
|
</button>
|
|
</h2>
|
|
<div id="collapseHowJpgWebp" class="accordion-collapse collapse" aria-labelledby="headingHowJpgWebp" data-bs-parent="#jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-body">
|
|
@ViewBag.FaqHowContent
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingWhyJpgWebp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseWhyJpgWebp" aria-expanded="false" aria-controls="collapseWhyJpgWebp">
|
|
<i class="bi bi-lightbulb me-2"></i>@ViewBag.FaqWhyTitle
|
|
</button>
|
|
</h2>
|
|
<div id="collapseWhyJpgWebp" class="accordion-collapse collapse" aria-labelledby="headingWhyJpgWebp" data-bs-parent="#jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-body">
|
|
@ViewBag.FaqWhyContent
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingSecurityJpgWebp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSecurityJpgWebp" aria-expanded="false" aria-controls="collapseSecurityJpgWebp">
|
|
<i class="bi bi-shield-check me-2"></i>@ViewBag.FaqSecurityTitle
|
|
</button>
|
|
</h2>
|
|
<div id="collapseSecurityJpgWebp" class="accordion-collapse collapse" aria-labelledby="headingSecurityJpgWebp" data-bs-parent="#jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-body">
|
|
@ViewBag.FaqSecurityContent
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="accordion-item">
|
|
<h2 class="accordion-header" id="headingLimitsJpgWebp">
|
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLimitsJpgWebp" aria-expanded="false" aria-controls="collapseLimitsJpgWebp">
|
|
<i class="bi bi-exclamation-triangle me-2"></i>@ViewBag.FaqLimitsTitle
|
|
</button>
|
|
</h2>
|
|
<div id="collapseLimitsJpgWebp" class="accordion-collapse collapse" aria-labelledby="headingLimitsJpgWebp" data-bs-parent="#jpgWebpConverterFaqAccordion">
|
|
<div class="accordion-body">
|
|
@ViewBag.FaqLimitsContent
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.nav-tabs .nav-link.disabled {
|
|
background-color: #f8f9fa;
|
|
border-color: #dee2e6;
|
|
color: #6c757d;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.nav-tabs .nav-link.disabled:hover {
|
|
background-color: #f8f9fa;
|
|
border-color: #dee2e6;
|
|
color: #6c757d;
|
|
}
|
|
|
|
#previewImage {
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.spinner-border {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
}
|
|
|
|
@@media (max-width: 576px) {
|
|
#previewImage {
|
|
max-height: 250px;
|
|
}
|
|
|
|
.card-header .btn {
|
|
font-size: 0.875rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const convertJpgPreviewBtn = document.getElementById('convertJpgPreviewBtn');
|
|
const convertWebpPreviewBtn = document.getElementById('convertWebpPreviewBtn');
|
|
const downloadBtn = document.getElementById('downloadBtn');
|
|
const fallbackDownloadBtn = document.getElementById('fallbackDownloadBtn');
|
|
const jpgForm = document.getElementById('jpgToWebpForm');
|
|
const webpForm = document.getElementById('webpToJpgForm');
|
|
const jpgFileInput = document.getElementById('jpgFile');
|
|
const webpFileInput = document.getElementById('webpFile');
|
|
const loadingSpinner = document.getElementById('loadingSpinner');
|
|
const previewArea = document.getElementById('previewArea');
|
|
const errorArea = document.getElementById('errorArea');
|
|
const previewImage = document.getElementById('previewImage');
|
|
const imageInfo = document.getElementById('imageInfo');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
|
|
let convertedImageData = null;
|
|
let convertedFileName = null;
|
|
|
|
// Bootstrap form validation
|
|
const forms = document.querySelectorAll('.needs-validation');
|
|
Array.from(forms).forEach(function(form) {
|
|
form.addEventListener('submit', function(event) {
|
|
if (!form.checkValidity()) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
form.classList.add('was-validated');
|
|
});
|
|
});
|
|
|
|
// File input validation
|
|
jpgFileInput.addEventListener('change', function() {
|
|
hideAllAreas();
|
|
|
|
if (this.files.length > 0) {
|
|
const file = this.files[0];
|
|
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
|
|
if (file.size > maxSize) {
|
|
showError('Arquivo muito grande para preview (máx. 10MB). Use "Converter e Baixar Diretamente".');
|
|
convertJpgPreviewBtn.disabled = true;
|
|
} else {
|
|
convertJpgPreviewBtn.disabled = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
webpFileInput.addEventListener('change', function() {
|
|
hideAllAreas();
|
|
|
|
if (this.files.length > 0) {
|
|
const file = this.files[0];
|
|
const maxSize = 10 * 1024 * 1024; // 10MB
|
|
|
|
if (file.size > maxSize) {
|
|
showError('Arquivo muito grande para preview (máx. 10MB). Use "Converter e Baixar Diretamente".');
|
|
convertWebpPreviewBtn.disabled = true;
|
|
} else {
|
|
convertWebpPreviewBtn.disabled = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Convert and Preview buttons
|
|
convertJpgPreviewBtn.addEventListener('click', async function() {
|
|
if (!jpgFileInput.files[0]) {
|
|
showError('Por favor, selecione um arquivo JPG primeiro.');
|
|
return;
|
|
}
|
|
|
|
await convertToPreview(jpgForm);
|
|
});
|
|
|
|
convertWebpPreviewBtn.addEventListener('click', async function() {
|
|
if (!webpFileInput.files[0]) {
|
|
showError('Por favor, selecione um arquivo WebP primeiro.');
|
|
return;
|
|
}
|
|
|
|
await convertToPreview(webpForm);
|
|
});
|
|
|
|
// Download button (after preview)
|
|
downloadBtn.addEventListener('click', function() {
|
|
if (convertedImageData && convertedFileName) {
|
|
downloadBase64Image(convertedImageData, convertedFileName);
|
|
}
|
|
});
|
|
|
|
// Fallback download button
|
|
fallbackDownloadBtn.addEventListener('click', function() {
|
|
const activeTab = document.querySelector('.nav-link.active');
|
|
const currentForm = activeTab.id === 'jpg-to-webp-tab' ? jpgForm : webpForm;
|
|
submitFormForDownload(currentForm);
|
|
});
|
|
|
|
async function convertToPreview(form) {
|
|
showLoading();
|
|
|
|
try {
|
|
const formData = new FormData(form);
|
|
|
|
const response = await fetch(form.action + '?preview=true', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (response.ok) {
|
|
const contentType = response.headers.get('content-type');
|
|
|
|
if (contentType && contentType.includes('application/json')) {
|
|
// JSON response with base64
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
showPreview(result.base64, result.filename, result.originalSize, result.convertedSize);
|
|
} else {
|
|
showError(result.message || 'Erro na conversão. Tentando download direto...');
|
|
setTimeout(() => submitFormForDownload(form), 2000);
|
|
}
|
|
} else {
|
|
// Binary response - fallback to download
|
|
showError('Arquivo muito grande para preview. Iniciando download direto...');
|
|
setTimeout(() => submitFormForDownload(form), 1000);
|
|
}
|
|
} else {
|
|
const errorText = await response.text();
|
|
showError('Erro na conversão: ' + (errorText || response.statusText));
|
|
}
|
|
} catch (error) {
|
|
console.error('Conversion error:', error);
|
|
showError('Erro de conexão. Tentando download direto...');
|
|
setTimeout(() => submitFormForDownload(form), 2000);
|
|
}
|
|
}
|
|
|
|
function showPreview(base64Data, filename, originalSize, convertedSize) {
|
|
hideAllAreas();
|
|
|
|
previewImage.src = base64Data;
|
|
convertedImageData = base64Data;
|
|
convertedFileName = filename;
|
|
|
|
const originalSizeMB = (originalSize / (1024 * 1024)).toFixed(2);
|
|
const convertedSizeMB = (convertedSize / (1024 * 1024)).toFixed(2);
|
|
const reduction = (((originalSize - convertedSize) / originalSize) * 100).toFixed(1);
|
|
|
|
imageInfo.textContent = `Original: ${originalSizeMB}MB → Convertido: ${convertedSizeMB}MB (${reduction}% menor)`;
|
|
|
|
previewArea.style.display = 'block';
|
|
}
|
|
|
|
function showLoading() {
|
|
hideAllAreas();
|
|
loadingSpinner.style.display = 'block';
|
|
}
|
|
|
|
function showError(message) {
|
|
hideAllAreas();
|
|
errorMessage.textContent = message;
|
|
errorArea.style.display = 'block';
|
|
}
|
|
|
|
function hideAllAreas() {
|
|
loadingSpinner.style.display = 'none';
|
|
previewArea.style.display = 'none';
|
|
errorArea.style.display = 'none';
|
|
}
|
|
|
|
function submitFormForDownload(form) {
|
|
// Submit form normally for direct download
|
|
if (form) {
|
|
form.submit();
|
|
}
|
|
}
|
|
|
|
function downloadBase64Image(base64Data, filename) {
|
|
// Convert base64 to blob and download
|
|
const base64Response = fetch(base64Data);
|
|
base64Response.then(res => res.blob()).then(blob => {
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
window.URL.revokeObjectURL(url);
|
|
});
|
|
}
|
|
});
|
|
</script> |