|
|
|
let selectedFiles = []; |
|
let completedFiles = []; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function () { |
|
setupEventListeners(); |
|
}); |
|
|
|
function setupEventListeners() { |
|
const uploadSection = document.getElementById('upload-section'); |
|
const fileInput = document.getElementById('file-input'); |
|
const processButton = document.getElementById('process-button'); |
|
const clearButton = document.getElementById('clear-button'); |
|
const downloadButton = document.getElementById('download-button'); |
|
|
|
|
|
uploadSection.addEventListener('click', () => fileInput.click()); |
|
uploadSection.addEventListener('dragover', handleDragOver); |
|
uploadSection.addEventListener('dragleave', handleDragLeave); |
|
uploadSection.addEventListener('drop', handleDrop); |
|
fileInput.addEventListener('change', handleFileSelect); |
|
|
|
|
|
processButton.addEventListener('click', processButtonFn); |
|
clearButton.addEventListener('click', clearAllFiles); |
|
downloadButton.addEventListener('click', downloadAllFiles); |
|
} |
|
|
|
function showMessage(message, type = 'error') { |
|
const errorMsg = document.getElementById('error-message'); |
|
const successMsg = document.getElementById('success-message'); |
|
|
|
|
|
errorMsg.style.display = 'none'; |
|
successMsg.style.display = 'none'; |
|
|
|
if (type === 'error') { |
|
errorMsg.textContent = message; |
|
errorMsg.style.display = 'block'; |
|
} else { |
|
successMsg.textContent = message; |
|
successMsg.style.display = 'block'; |
|
} |
|
|
|
|
|
setTimeout(() => { |
|
errorMsg.style.display = 'none'; |
|
successMsg.style.display = 'none'; |
|
}, 5000); |
|
} |
|
|
|
function handleDragOver(e) { |
|
e.preventDefault(); |
|
e.currentTarget.classList.add('dragover'); |
|
} |
|
|
|
function handleDragLeave(e) { |
|
e.preventDefault(); |
|
e.currentTarget.classList.remove('dragover'); |
|
} |
|
|
|
function handleDrop(e) { |
|
e.preventDefault(); |
|
const uploadSection = e.currentTarget; |
|
uploadSection.classList.remove('dragover'); |
|
|
|
const files = Array.from(e.dataTransfer.files); |
|
processFiles(files); |
|
} |
|
|
|
function handleFileSelect(e) { |
|
const files = Array.from(e.target.files); |
|
processFiles(files); |
|
} |
|
|
|
function generateUniqueId(file, length = 12) { |
|
ext = "." + file.name.split(".")[file.name.split(".").length - 1] |
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; |
|
return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext; |
|
} |
|
|
|
function saveFileIdToStorage(id) { |
|
let fileIds = JSON.parse(localStorage.getItem('uploadedFileIds') || '[]'); |
|
if (!fileIds.includes(id)) { |
|
fileIds.push(id); |
|
localStorage.setItem('uploadedFileIds', JSON.stringify(fileIds)); |
|
} |
|
} |
|
|
|
function processFiles(files) { |
|
files.forEach((file, index) => { |
|
const id = generateUniqueId(file); |
|
const fileData = { |
|
id: id, |
|
file: file, |
|
name: file.name, |
|
size: formatFileSize(file.size), |
|
status: 'ready', |
|
preview: null |
|
}; |
|
|
|
|
|
saveFileIdToStorage(id); |
|
|
|
|
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
fileData.preview = e.target.result; |
|
updateFileCard(fileData.id); |
|
}; |
|
reader.readAsDataURL(file); |
|
|
|
selectedFiles.push(fileData); |
|
}); |
|
|
|
updateUI(); |
|
} |
|
|
|
function updateUI() { |
|
const filesPreview = document.getElementById('files-preview'); |
|
const processButton = document.getElementById('process-button'); |
|
|
|
if (selectedFiles.length > 0) { |
|
filesPreview.style.display = 'block'; |
|
processButton.disabled = false; |
|
renderFileCards(); |
|
} else { |
|
filesPreview.style.display = 'none'; |
|
processButton.disabled = true; |
|
} |
|
} |
|
|
|
function renderFileCards() { |
|
const filesGrid = document.getElementById('files-grid'); |
|
|
|
filesGrid.innerHTML = selectedFiles.map(file => ` |
|
<div class="file-card" id="file-${file.id}"> |
|
<button class="remove-button" onclick="removeFile('${file.id}')">×</button> |
|
${window.location.pathname == '/image/remove_metadata' && file.other_info ? `<button class="remove-button" style="top: 40px; background: #17a2b8;" onclick="showMetadata('${file.id}')">i</button>`: ``} |
|
|
|
<div class="file-preview"> |
|
${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : '<span class="file-icon">🖼️</span>'} |
|
</div> |
|
|
|
<div class="file-info"> |
|
<h4>${file.name}</h4> |
|
<p>${file.size}</p> |
|
|
|
<div class="file-status"> |
|
<div class="status-indicator ${file.status === 'processing' ? 'processing' : file.status === 'error' ? 'error' : ''}"></div> |
|
<span>${getStatusText(file.status)}</span> |
|
</div> |
|
</div> |
|
</div> |
|
`).join(''); |
|
} |
|
|
|
function updateFileCard(fileId) { |
|
const file = selectedFiles.find(f => f.id === fileId); |
|
if (!file) return; |
|
|
|
const card = document.getElementById(`file-${fileId}`); |
|
if (!card) return; |
|
|
|
const preview = card.querySelector('.file-preview'); |
|
if (file.preview && preview) { |
|
preview.innerHTML = `<img src="${file.preview}" alt="${file.name}">`; |
|
} |
|
} |
|
|
|
async function processButtonFn() { |
|
if (selectedFiles.length === 0) return; |
|
|
|
const progressSection = document.getElementById('progress-section'); |
|
const progressBar = document.getElementById('progress-bar'); |
|
const progressText = document.getElementById('progress-text'); |
|
const processButton = document.getElementById('process-button'); |
|
|
|
|
|
progressSection.style.display = 'block'; |
|
processButton.disabled = true; |
|
const old_message = processButton.innerHTML; |
|
processButton.innerHTML = '🔄 Processing...'; |
|
const settings = getSettingsData(); |
|
completedFiles = []; |
|
|
|
for (let i = 0; i < selectedFiles.length; i++) { |
|
const file = selectedFiles[i]; |
|
|
|
|
|
file.status = 'uploading'; |
|
renderFileCards(); |
|
|
|
try { |
|
|
|
await upload(file); |
|
file.status = 'processing'; |
|
renderFileCards(); |
|
const processedFile = await secret_sauce(file, settings); |
|
file.status = 'completed'; |
|
file.other_info = processedFile.other_info; |
|
renderFileCards(); |
|
completedFiles.push(processedFile); |
|
} catch (error) { |
|
console.error('Processing error:', error); |
|
file.status = 'error'; |
|
showMessage(`Error processing ${file.name}: ${error.message}`, 'error'); |
|
} |
|
|
|
|
|
const progress = Math.round(((i + 1) / selectedFiles.length) * 100); |
|
progressBar.style.width = progress + '%'; |
|
progressText.textContent = `${progress}% complete (${i + 1}/${selectedFiles.length})`; |
|
|
|
renderFileCards(); |
|
} |
|
|
|
|
|
processButton.disabled = false; |
|
processButton.innerHTML = old_message; |
|
|
|
const successfulProcessing = completedFiles.length; |
|
if (successfulProcessing > 0) { |
|
document.getElementById('download-button').style.display = 'inline-flex'; |
|
showMessage(`Successfully processed ${successfulProcessing} image(s)!`, 'success'); |
|
} |
|
|
|
setTimeout(() => { |
|
progressSection.style.display = 'none'; |
|
}, 1000); |
|
} |
|
|
|
async function secret_sauce(file, settings) { |
|
const formData = new FormData(); |
|
formData.append('id', file.id); |
|
for (let key of Object.keys(settings)) { |
|
if (settings[key]) { |
|
formData.append('to_format', settings[key]); |
|
} |
|
} |
|
|
|
try { |
|
const response = await fetch(secret_sauce_url(), { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
const errorData = await response.json().catch(() => ({})); |
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`); |
|
} |
|
|
|
const result = await response.json(); |
|
|
|
return { |
|
id: file.id, |
|
originalName: file.name, |
|
processedName: result.new_filename, |
|
url: "/image/download?id=" + result.new_filename, |
|
other_info: result.other_info |
|
}; |
|
} catch (error) { |
|
throw new Error(`Failed to process ${file.name}: ${error.message}`); |
|
} |
|
} |
|
|
|
function downloadAllFiles() { |
|
if (completedFiles.length === 0) return; |
|
|
|
|
|
completedFiles.forEach((file, index) => { |
|
setTimeout(() => { |
|
const link = document.createElement('a'); |
|
link.href = file.url; |
|
link.download = file.processedName; |
|
link.target = '_blank'; |
|
document.body.appendChild(link); |
|
link.click(); |
|
document.body.removeChild(link); |
|
}, index * 100); |
|
}); |
|
} |
|
|
|
function formatFileSize(bytes) { |
|
if (bytes === 0) return '0 Bytes'; |
|
const k = 1024; |
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
} |
|
|
|
function removeFile(fileId) { |
|
selectedFiles = selectedFiles.filter(file => file.id !== fileId); |
|
completedFiles = completedFiles.filter(file => file.id !== fileId); |
|
updateUI(); |
|
|
|
if (selectedFiles.length === 0) { |
|
document.getElementById('download-button').style.display = 'none'; |
|
} |
|
} |
|
|
|
async function clearAllFiles() { |
|
const fileIds = JSON.parse(localStorage.getItem('uploadedFileIds') || '[]'); |
|
|
|
if (fileIds.length > 0) { |
|
try { |
|
await fetch('/image/delete', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ ids: fileIds }) |
|
}); |
|
} catch (err) { |
|
console.error('Error sending delete request:', err); |
|
} |
|
} |
|
|
|
|
|
selectedFiles = []; |
|
completedFiles = []; |
|
updateUI(); |
|
document.getElementById('download-button').style.display = 'none'; |
|
document.getElementById('progress-section').style.display = 'none'; |
|
localStorage.removeItem('uploadedFileIds'); |
|
} |
|
|
|
async function upload(file) { |
|
const formData = new FormData(); |
|
formData.append('image', file.file); |
|
formData.append('id', file.id); |
|
|
|
try { |
|
const response = await fetch('/image/upload', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
const errorData = await response.json().catch(() => ({})); |
|
throw new Error(errorData.message || `HTTP error! status: ${response.status}`); |
|
} |
|
return true; |
|
} catch (error) { |
|
throw new Error(`Failed to upload ${file.name}: ${error.message}`); |
|
} |
|
} |
|
|
|
function getSettingsData() { |
|
return { |
|
to_format: document.getElementById('output-format')?.value |
|
}; |
|
} |
|
|
|
function getStatusText(status) { |
|
switch (status) { |
|
case 'ready': return 'Ready'; |
|
case 'uploading': return 'Uploading...'; |
|
case 'processing': return window.location.pathname == '/image/remove_metadata' ? 'Removing metadata...' : 'Converting...'; |
|
case 'completed': return 'Processed'; |
|
case 'error': return 'Error'; |
|
default: return 'Unknown'; |
|
} |
|
} |
|
function showMetadata(fileId) { |
|
const file = selectedFiles.find(f => f.id === fileId); |
|
const contentEl = document.getElementById('metadata-content'); |
|
if (file && file.other_info) { |
|
contentEl.textContent = JSON.stringify(file.other_info, null, 2); |
|
document.getElementById('metadata-modal').style.display = 'block'; |
|
document.getElementById('modal-backdrop').style.display = 'block'; |
|
} else { |
|
contentEl.textContent = 'No metadata removed or unavailable.'; |
|
document.getElementById('metadata-modal').style.display = 'block'; |
|
document.getElementById('modal-backdrop').style.display = 'block'; |
|
} |
|
} |
|
|
|
function closeMetadataModal() { |
|
document.getElementById('metadata-modal').style.display = 'none'; |
|
document.getElementById('modal-backdrop').style.display = 'none'; |
|
} |
|
|