jebin2's picture
issue
8f4f5c6
raw
history blame
16.5 kB
let selectedFiles = [];
let completedFiles = [];
// Constants
const MAX_AGE_DAYS = 2;
const MAX_AGE_MS = MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
// Initialize the application
document.addEventListener('DOMContentLoaded', function () {
cleanupOrphanedStorageEntries();
setupEventListeners();
});
/**
* Remove localStorage entries older than MAX_AGE_DAYS
* This prevents orphaned entries from accumulating
*/
function cleanupOrphanedStorageEntries() {
try {
const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
const now = Date.now();
let cleaned = false;
for (const id in storageData) {
if (now - storageData[id].timestamp > MAX_AGE_MS) {
delete storageData[id];
cleaned = true;
}
}
if (cleaned) {
localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
console.log('Cleaned orphaned localStorage entries');
}
// Also clean legacy format if exists
localStorage.removeItem('uploadedFileIds');
} catch (e) {
console.warn('Failed to cleanup localStorage:', e);
}
}
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');
// File upload events
uploadSection.addEventListener('click', () => fileInput.click());
uploadSection.addEventListener('dragover', handleDragOver);
uploadSection.addEventListener('dragleave', handleDragLeave);
uploadSection.addEventListener('drop', handleDrop);
fileInput.addEventListener('change', handleFileSelect);
// Button events
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');
// Hide both messages first
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';
}
// Auto-hide after 5 seconds
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) {
let 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;
}
/**
* Save file ID with timestamp to localStorage
*/
function saveFileIdToStorage(id) {
const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
if (!storageData[id]) {
storageData[id] = {
timestamp: Date.now()
};
localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
}
}
/**
* Remove file ID from localStorage
*/
function removeFileIdFromStorage(id) {
const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
delete storageData[id];
localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
}
function processFiles(files) {
files.forEach((file, index) => {
const id = generateUniqueId(file); // generate unique ID
const fileData = {
id: id,
file: file,
name: file.name,
size: formatFileSize(file.size),
status: 'ready',
preview: null
};
// Store file ID for potential server deletion later
saveFileIdToStorage(id);
// Create preview for images
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 => {
// Find completed file data for download URL
const completedFile = completedFiles.find(cf => cf.id === file.id);
// Check if metadata info exists and has content
const hasMetadata = file.other_info && Object.keys(file.other_info).length > 0;
// Calculate button positions based on page type
const isMetadataPage = window.location.pathname == '/image/remove_metadata';
const downloadTop = isMetadataPage ? '70px' : '40px';
const viewTop = isMetadataPage ? '100px' : '70px';
return `
<div class="file-card" id="file-${file.id}">
<button class="remove-button" onclick="removeFile('${file.id}')">×</button>
${isMetadataPage && file.status === 'completed' ? `<button class="remove-button" style="top: 40px; background: #17a2b8;" onclick="showMetadata('${file.id}')" title="View removed metadata">i</button>` : ``}
${file.status === 'error' && file.errorMessage ? `<button class="remove-button" style="top: 40px; background: #dc3545;" onclick="showError('${file.id}')" title="View error">!</button>` : ``}
${file.status === 'completed' && completedFile ? `<button class="remove-button" style="top: ${downloadTop}; background: #28a745;" onclick="downloadFile('${file.id}')" title="Download">⬇</button>` : ``}
${file.status === 'completed' && completedFile ? `<button class="remove-button" style="top: ${viewTop}; background: #6f42c1;" onclick="viewImage('${file.id}')" title="View image">👁</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' : file.status === 'completed' ? 'completed' : ''}"></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');
// Show progress section
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];
// Update file status
file.status = 'uploading';
renderFileCards();
try {
// Upload and process the file
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';
file.errorMessage = error.message;
showMessage(`Error processing ${file.name}: ${error.message}`, 'error');
}
// Update progress
const progress = Math.round(((i + 1) / selectedFiles.length) * 100);
progressBar.style.width = progress + '%';
progressText.textContent = `${progress}% complete (${i + 1}/${selectedFiles.length})`;
renderFileCards();
}
// Completion
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;
// Download each completedFiles file
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);
removeFileIdFromStorage(fileId);
updateUI();
if (selectedFiles.length === 0) {
document.getElementById('download-button').style.display = 'none';
}
}
async function clearAllFiles() {
const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
const fileIds = Object.keys(storageData);
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);
}
}
// Clear everything locally
selectedFiles = [];
completedFiles = [];
updateUI();
document.getElementById('download-button').style.display = 'none';
document.getElementById('progress-section').style.display = 'none';
localStorage.removeItem('uploadedFileData');
}
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';
}
/**
* Show error message for a file in a modal/alert
*/
function showError(fileId) {
const file = selectedFiles.find(f => f.id === fileId);
if (file && file.errorMessage) {
// Use showMessage which displays at the top of the page
showMessage(`Error: ${file.errorMessage}`, 'error');
} else {
showMessage('Unknown error occurred', 'error');
}
}
/**
* Download a single processed file
*/
function downloadFile(fileId) {
const completedFile = completedFiles.find(f => f.id === fileId);
if (completedFile) {
const link = document.createElement('a');
link.href = completedFile.url;
link.download = completedFile.processedName;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
/**
* View the processed image in a modal popup
*/
function viewImage(fileId) {
const completedFile = completedFiles.find(f => f.id === fileId);
if (completedFile) {
// Create modal if it doesn't exist
let modal = document.getElementById('image-view-modal');
let backdrop = document.getElementById('image-view-backdrop');
if (!modal) {
// Create backdrop
backdrop = document.createElement('div');
backdrop.id = 'image-view-backdrop';
backdrop.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 9998; display: none;';
backdrop.onclick = closeImageModal;
document.body.appendChild(backdrop);
// Create modal
modal = document.createElement('div');
modal.id = 'image-view-modal';
modal.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 9999; display: none; max-width: 90%; max-height: 90%; background: white; border-radius: 10px; padding: 10px; box-shadow: 0 0 30px rgba(0,0,0,0.5);';
modal.innerHTML = `
<button onclick="closeImageModal()" style="position: absolute; top: -10px; right: -10px; width: 30px; height: 30px; border-radius: 50%; border: none; background: #dc3545; color: white; font-size: 18px; cursor: pointer; z-index: 10000;">×</button>
<img id="image-view-content" style="max-width: 80vw; max-height: 80vh; display: block; border-radius: 5px;" />
`;
document.body.appendChild(modal);
}
// Show modal with image
document.getElementById('image-view-content').src = completedFile.url;
modal.style.display = 'block';
backdrop.style.display = 'block';
}
}
/**
* Close image view modal
*/
function closeImageModal() {
const modal = document.getElementById('image-view-modal');
const backdrop = document.getElementById('image-view-backdrop');
if (modal) modal.style.display = 'none';
if (backdrop) backdrop.style.display = 'none';
}