Improve EXIF tool error handling and UX

- Add loading spinner feedback for Clear All and Save buttons
- Show error alerts when requests fail instead of silent failure
- Detect session expiration and redirect to login
- Update UI to show empty state after clearing metadata
- Fix download by properly appending anchor to DOM

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-04 12:23:27 -05:00
parent ed1d230b4e
commit d71f615d66

View File

@@ -309,6 +309,10 @@ document.getElementById('exifClearAll')?.addEventListener('click', async functio
formData.append('image', exifCurrentFile); formData.append('image', exifCurrentFile);
formData.append('format', 'PNG'); formData.append('format', 'PNG');
const btn = this;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Clearing...';
try { try {
const res = await fetch('/api/tools/exif/clear', { method: 'POST', body: formData }); const res = await fetch('/api/tools/exif/clear', { method: 'POST', body: formData });
if (res.ok) { if (res.ok) {
@@ -317,11 +321,34 @@ document.getElementById('exifClearAll')?.addEventListener('click', async functio
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = res.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'clean.png'; a.download = res.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'clean.png';
document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
// Clear the current data to show empty state
exifCurrentData = {};
exifOriginalData = {};
renderExifTable();
} else {
// Try to parse error
const contentType = res.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const err = await res.json();
alert(err.error || 'Failed to clear metadata');
} else if (res.status === 401 || res.status === 302) {
alert('Session expired. Please log in again.');
window.location.href = '/login';
} else {
alert(`Failed to clear metadata (${res.status})`);
}
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
alert('Failed to clear metadata: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-trash me-1"></i>Clear All';
} }
}); });
@@ -356,6 +383,11 @@ document.getElementById('exifSave')?.addEventListener('click', async function()
formData.append('image', exifCurrentFile); formData.append('image', exifCurrentFile);
formData.append('updates', JSON.stringify(updates)); formData.append('updates', JSON.stringify(updates));
const btn = this;
const originalHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Saving...';
try { try {
const res = await fetch('/api/tools/exif/update', { method: 'POST', body: formData }); const res = await fetch('/api/tools/exif/update', { method: 'POST', body: formData });
if (res.ok) { if (res.ok) {
@@ -364,19 +396,33 @@ document.getElementById('exifSave')?.addEventListener('click', async function()
const a = document.createElement('a'); const a = document.createElement('a');
a.href = url; a.href = url;
a.download = res.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'updated.jpg'; a.download = res.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/"/g, '') || 'updated.jpg';
document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
// Update original to match current // Update original to match current
exifOriginalData = JSON.parse(JSON.stringify(exifCurrentData)); exifOriginalData = JSON.parse(JSON.stringify(exifCurrentData));
updateSaveButton(); updateSaveButton();
} else { } else {
const contentType = res.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const err = await res.json(); const err = await res.json();
alert(err.error || 'Failed to save'); alert(err.error || 'Failed to save');
} else if (res.status === 401 || res.status === 302) {
alert('Session expired. Please log in again.');
window.location.href = '/login';
} else {
alert(`Failed to save (${res.status})`);
}
} }
} catch (err) { } catch (err) {
console.error(err); console.error(err);
alert('Failed to save changes'); alert('Failed to save changes: ' + err.message);
} finally {
btn.disabled = false;
btn.innerHTML = originalHtml;
updateSaveButton();
} }
}); });