Expanded UI
This commit is contained in:
716
js/enhanced-history.js
Normal file
716
js/enhanced-history.js
Normal file
@@ -0,0 +1,716 @@
|
||||
import { showToast, addRippleEffect } from './utils.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const selectionGrid = document.getElementById('selection-grid');
|
||||
const filterButtons = document.querySelectorAll('.filter-buttons .filter-btn');
|
||||
const orientationButtons = document.querySelectorAll('.orientation-filters .filter-btn');
|
||||
const resolutionFilter = document.getElementById('resolution-filter');
|
||||
const selectAllBtn = document.getElementById('select-all');
|
||||
const deselectAllBtn = document.getElementById('deselect-all');
|
||||
const downloadSelectedBtn = document.getElementById('download-selected');
|
||||
const filteredCountEl = document.getElementById('filtered-count');
|
||||
|
||||
// Add ripple effect to all action buttons
|
||||
document.querySelectorAll('.action-btn').forEach(button => {
|
||||
addRippleEffect(button);
|
||||
});
|
||||
|
||||
const actionModal = document.getElementById('action-modal');
|
||||
const closeActionModal = document.getElementById('close-action-modal');
|
||||
const actionButtons = actionModal.querySelectorAll('.action-btn');
|
||||
const modalPreviewImg = document.getElementById('modal-preview-img');
|
||||
const modalMessage = document.getElementById('modal-message');
|
||||
|
||||
const resetBtn = document.getElementById('reset-db');
|
||||
const resetModal = document.getElementById('reset-modal');
|
||||
const confirmResetBtn = document.getElementById('confirm-reset');
|
||||
const cancelResetBtn = document.getElementById('cancel-reset');
|
||||
const resetMessage = document.getElementById('reset-message');
|
||||
|
||||
let currentFilter = 'all';
|
||||
let currentOrientation = 'all';
|
||||
let currentResolution = 'all';
|
||||
let selectedItems = [];
|
||||
let currentSelectionId = null;
|
||||
let allSelections = [];
|
||||
|
||||
// Enhanced loading animation
|
||||
function showLoading() {
|
||||
selectionGrid.classList.add('loading');
|
||||
selectionGrid.innerHTML = `
|
||||
<div class="loading-container">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">Loading selections...</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function hideLoading() {
|
||||
selectionGrid.classList.remove('loading');
|
||||
}
|
||||
|
||||
const loadSelections = () => {
|
||||
showLoading();
|
||||
|
||||
fetch('/selections')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
hideLoading();
|
||||
|
||||
if (data.selections && data.selections.length > 0) {
|
||||
allSelections = data.selections;
|
||||
populateResolutionFilter(data.selections);
|
||||
renderSelections(data.selections);
|
||||
|
||||
// Show stats
|
||||
const stats = calculateStats(data.selections);
|
||||
updateStats(stats);
|
||||
|
||||
showToast(`Loaded ${data.selections.length} images`);
|
||||
} else {
|
||||
selectionGrid.innerHTML = `
|
||||
<div class="no-selections">
|
||||
<i class="fa-solid fa-image-slash fa-3x"></i>
|
||||
<p>No selections found</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
hideLoading();
|
||||
console.error('Error loading selections:', error);
|
||||
selectionGrid.innerHTML = `
|
||||
<div class="error">
|
||||
<i class="fa-solid fa-triangle-exclamation fa-3x"></i>
|
||||
<p>Error loading selections: ${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
showToast('Error loading selections', 'error');
|
||||
});
|
||||
};
|
||||
|
||||
const calculateStats = (selections) => {
|
||||
const stats = {
|
||||
total: selections.length,
|
||||
byAction: {
|
||||
left: selections.filter(s => s.action === 'left').length,
|
||||
right: selections.filter(s => s.action === 'right').length,
|
||||
up: selections.filter(s => s.action === 'up').length,
|
||||
down: selections.filter(s => s.action === 'down').length
|
||||
},
|
||||
byOrientation: {
|
||||
portrait: selections.filter(s => s.orientation === 'portrait').length,
|
||||
landscape: selections.filter(s => s.orientation === 'landscape').length,
|
||||
square: selections.filter(s => s.orientation === 'square').length
|
||||
}
|
||||
};
|
||||
|
||||
return stats;
|
||||
};
|
||||
|
||||
const updateStats = (stats) => {
|
||||
const statsContainer = document.getElementById('stats-container');
|
||||
if (!statsContainer) return;
|
||||
|
||||
statsContainer.innerHTML = `
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${stats.total}</span>
|
||||
<span class="stat-label">Total Images</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${stats.byAction.left}</span>
|
||||
<span class="stat-label">Discarded</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${stats.byAction.right}</span>
|
||||
<span class="stat-label">Kept</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${stats.byAction.up}</span>
|
||||
<span class="stat-label">Favorited</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-value">${stats.byAction.down}</span>
|
||||
<span class="stat-label">For Review</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add animation to stats
|
||||
const statItems = statsContainer.querySelectorAll('.stat-item');
|
||||
statItems.forEach((item, index) => {
|
||||
item.style.opacity = 0;
|
||||
item.style.transform = 'translateY(20px)';
|
||||
|
||||
setTimeout(() => {
|
||||
item.style.transition = 'all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
|
||||
item.style.opacity = 1;
|
||||
item.style.transform = 'translateY(0)';
|
||||
}, 100 * index);
|
||||
});
|
||||
};
|
||||
|
||||
const populateResolutionFilter = (selections) => {
|
||||
const resolutions = [...new Set(selections.map(s => s.resolution))].sort();
|
||||
resolutionFilter.innerHTML = '<option value="all">All Resolutions</option>';
|
||||
resolutions.forEach(resolution => {
|
||||
const option = document.createElement('option');
|
||||
option.value = resolution;
|
||||
option.textContent = resolution;
|
||||
resolutionFilter.appendChild(option);
|
||||
});
|
||||
};
|
||||
|
||||
const renderSelections = (selections) => {
|
||||
selectionGrid.innerHTML = '';
|
||||
|
||||
const filteredSelections = selections.filter(s =>
|
||||
(currentFilter === 'all' || s.action === currentFilter) &&
|
||||
(currentOrientation === 'all' || s.orientation === currentOrientation) &&
|
||||
(currentResolution === 'all' || s.resolution === currentResolution)
|
||||
);
|
||||
|
||||
if (filteredSelections.length === 0) {
|
||||
selectionGrid.innerHTML = `
|
||||
<div class="no-selections">
|
||||
<i class="fa-solid fa-filter-circle-xmark fa-3x"></i>
|
||||
<p>No selections match the current filters</p>
|
||||
</div>
|
||||
`;
|
||||
filteredCountEl.textContent = `0 images match your filters (out of ${selections.length} total)`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the filtered count
|
||||
if (filteredCountEl) {
|
||||
filteredCountEl.textContent = `Showing ${filteredSelections.length} of ${selections.length} images`;
|
||||
}
|
||||
|
||||
// Create a document fragment for better performance
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
filteredSelections.forEach((selection, index) => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'selection-item';
|
||||
item.dataset.id = selection.id;
|
||||
|
||||
// Add animation delay for staggered appearance
|
||||
const delay = Math.min(0.05 * index, 1);
|
||||
item.style.animationDelay = `${delay}s`;
|
||||
|
||||
const actionName = getActionName(selection.action);
|
||||
const actionIcon = getActionIcon(selection.action);
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="selection-checkbox-container">
|
||||
<input type="checkbox" class="selection-checkbox" id="checkbox-${selection.id}">
|
||||
<label for="checkbox-${selection.id}" class="checkbox-label"></label>
|
||||
</div>
|
||||
<div class="image-container">
|
||||
<img src="${selection.image_path}" alt="${actionName} image" loading="lazy">
|
||||
</div>
|
||||
<div class="selection-action action-${selection.action}">
|
||||
<i class="fa-solid ${actionIcon}"></i> ${actionName}
|
||||
</div>
|
||||
<div class="selection-info">
|
||||
<p class="filename">${selection.image_path.split('/').pop()}</p>
|
||||
<p class="resolution">Resolution: ${selection.resolution}</p>
|
||||
<p class="timestamp">Date: ${formatDate(selection.timestamp)}</p>
|
||||
</div>
|
||||
<div class="selection-controls">
|
||||
<button class="control-btn edit-btn" title="Change action">
|
||||
<i class="fa-solid fa-pen-to-square"></i>
|
||||
</button>
|
||||
<button class="control-btn view-btn" title="View full size">
|
||||
<i class="fa-solid fa-expand"></i>
|
||||
</button>
|
||||
<button class="control-btn delete-btn" title="Remove">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add the item to the fragment
|
||||
fragment.appendChild(item);
|
||||
});
|
||||
|
||||
// Append all items at once
|
||||
selectionGrid.appendChild(fragment);
|
||||
|
||||
// Add fade-in animation class
|
||||
setTimeout(() => {
|
||||
selectionGrid.classList.add('loaded');
|
||||
}, 10);
|
||||
};
|
||||
|
||||
const formatDate = (timestamp) => {
|
||||
if (!timestamp) return 'Unknown';
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleDateString();
|
||||
};
|
||||
|
||||
const getActionName = (action) => {
|
||||
const names = { left: 'Discard', right: 'Keep', up: 'Favorite', down: 'Review' };
|
||||
return names[action] || action;
|
||||
};
|
||||
|
||||
const getActionIcon = (action) => {
|
||||
const icons = { left: 'fa-trash', right: 'fa-folder-plus', up: 'fa-star', down: 'fa-clock' };
|
||||
return icons[action] || 'fa-question';
|
||||
};
|
||||
|
||||
const updateDownloadButton = () => {
|
||||
downloadSelectedBtn.disabled = selectedItems.length === 0;
|
||||
downloadSelectedBtn.querySelector('.label').textContent = selectedItems.length > 0 ? `Download (${selectedItems.length})` : 'Download';
|
||||
};
|
||||
|
||||
// Enhanced selection item click handler
|
||||
selectionGrid.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
const selectionItem = target.closest('.selection-item');
|
||||
if (!selectionItem) return;
|
||||
|
||||
const selectionId = selectionItem.dataset.id;
|
||||
const selection = {
|
||||
id: selectionId,
|
||||
image_path: selectionItem.querySelector('img').src,
|
||||
action: selectionItem.querySelector('.selection-action').classList[1].replace('action-', '')
|
||||
};
|
||||
|
||||
// Handle checkbox click
|
||||
if (target.classList.contains('selection-checkbox') || target.classList.contains('checkbox-label')) {
|
||||
const checkbox = selectionItem.querySelector('.selection-checkbox');
|
||||
const isChecked = checkbox.checked;
|
||||
|
||||
if (isChecked) {
|
||||
selectionItem.classList.add('selected');
|
||||
selectedItems.push(selection);
|
||||
showToast(`Selected image (${selectedItems.length} total)`);
|
||||
} else {
|
||||
selectionItem.classList.remove('selected');
|
||||
selectedItems = selectedItems.filter(item => item.id !== selectionId);
|
||||
showToast(`Deselected image (${selectedItems.length} total)`);
|
||||
}
|
||||
updateDownloadButton();
|
||||
}
|
||||
// Handle edit button click
|
||||
else if (target.classList.contains('edit-btn') || target.closest('.edit-btn')) {
|
||||
currentSelectionId = selectionId;
|
||||
modalPreviewImg.src = selection.image_path;
|
||||
|
||||
// Highlight the current action in the modal
|
||||
actionButtons.forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
if (btn.dataset.action === selection.action) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
actionModal.style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
actionModal.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
// Handle view button click
|
||||
else if (target.classList.contains('view-btn') || target.closest('.view-btn')) {
|
||||
// Open image in fullscreen modal
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal fullscreen-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<span class="close-modal">×</span>
|
||||
<img src="${selection.image_path}" alt="Full size image">
|
||||
<div class="modal-info">
|
||||
<p>Action: <span class="action-${selection.action}">${getActionName(selection.action)}</span></p>
|
||||
<p>Filename: ${selection.image_path.split('/').pop()}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Show the modal with animation
|
||||
setTimeout(() => {
|
||||
modal.style.display = 'flex';
|
||||
setTimeout(() => modal.classList.add('show'), 10);
|
||||
}, 10);
|
||||
|
||||
// Add close functionality
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target === modal || e.target.classList.contains('close-modal')) {
|
||||
modal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
modal.style.display = 'none';
|
||||
modal.remove();
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Handle delete button click
|
||||
else if (target.classList.contains('delete-btn') || target.closest('.delete-btn')) {
|
||||
// Create a confirmation dialog
|
||||
const confirmDialog = document.createElement('div');
|
||||
confirmDialog.className = 'modal confirmation-modal';
|
||||
confirmDialog.innerHTML = `
|
||||
<div class="modal-content">
|
||||
<h3>Confirm Deletion</h3>
|
||||
<p>Are you sure you want to delete this selection?</p>
|
||||
<div class="confirmation-buttons">
|
||||
<button class="action-btn confirm-delete-btn">Yes, Delete</button>
|
||||
<button class="action-btn cancel-delete-btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(confirmDialog);
|
||||
|
||||
// Show the dialog with animation
|
||||
setTimeout(() => {
|
||||
confirmDialog.style.display = 'flex';
|
||||
setTimeout(() => confirmDialog.classList.add('show'), 10);
|
||||
}, 10);
|
||||
|
||||
// Add button functionality
|
||||
confirmDialog.querySelector('.confirm-delete-btn').addEventListener('click', () => {
|
||||
// Add animation before removing
|
||||
selectionItem.classList.add('removing');
|
||||
|
||||
// Close the dialog
|
||||
confirmDialog.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
confirmDialog.style.display = 'none';
|
||||
confirmDialog.remove();
|
||||
}, 400);
|
||||
|
||||
// Simulate delete functionality (replace with actual API call)
|
||||
setTimeout(() => {
|
||||
selectionItem.remove();
|
||||
showToast('Selection removed');
|
||||
|
||||
// Update selected items if this was selected
|
||||
if (selectedItems.some(item => item.id === selectionId)) {
|
||||
selectedItems = selectedItems.filter(item => item.id !== selectionId);
|
||||
updateDownloadButton();
|
||||
}
|
||||
|
||||
// Update allSelections array
|
||||
allSelections = allSelections.filter(s => s.id !== selectionId);
|
||||
|
||||
// Update stats
|
||||
const stats = calculateStats(allSelections);
|
||||
updateStats(stats);
|
||||
|
||||
// Update filtered count
|
||||
if (filteredCountEl) {
|
||||
const filteredSelections = allSelections.filter(s =>
|
||||
(currentFilter === 'all' || s.action === currentFilter) &&
|
||||
(currentOrientation === 'all' || s.orientation === currentOrientation) &&
|
||||
(currentResolution === 'all' || s.resolution === currentResolution)
|
||||
);
|
||||
filteredCountEl.textContent = `Showing ${filteredSelections.length} of ${allSelections.length} images`;
|
||||
}
|
||||
}, 300);
|
||||
});
|
||||
|
||||
confirmDialog.querySelector('.cancel-delete-btn').addEventListener('click', () => {
|
||||
confirmDialog.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
confirmDialog.style.display = 'none';
|
||||
confirmDialog.remove();
|
||||
}, 400);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Enhanced filter button click handlers
|
||||
filterButtons.forEach(button => button.addEventListener('click', function() {
|
||||
filterButtons.forEach(btn => btn.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
currentFilter = this.dataset.filter;
|
||||
|
||||
// Apply filter animation
|
||||
selectionGrid.classList.add('filtering');
|
||||
setTimeout(() => {
|
||||
renderSelections(allSelections);
|
||||
selectionGrid.classList.remove('filtering');
|
||||
}, 300);
|
||||
}));
|
||||
|
||||
orientationButtons.forEach(button => button.addEventListener('click', function() {
|
||||
orientationButtons.forEach(btn => btn.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
currentOrientation = this.dataset.orientation;
|
||||
|
||||
// Apply filter animation
|
||||
selectionGrid.classList.add('filtering');
|
||||
setTimeout(() => {
|
||||
renderSelections(allSelections);
|
||||
selectionGrid.classList.remove('filtering');
|
||||
}, 300);
|
||||
}));
|
||||
|
||||
resolutionFilter.addEventListener('change', function() {
|
||||
currentResolution = this.value;
|
||||
|
||||
// Apply filter animation
|
||||
selectionGrid.classList.add('filtering');
|
||||
setTimeout(() => {
|
||||
renderSelections(allSelections);
|
||||
selectionGrid.classList.remove('filtering');
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Enhanced select/deselect all functionality
|
||||
selectAllBtn.addEventListener('click', () => {
|
||||
const checkboxes = document.querySelectorAll('.selection-checkbox');
|
||||
checkboxes.forEach(cb => cb.checked = true);
|
||||
|
||||
selectedItems = Array.from(document.querySelectorAll('.selection-item')).map(item => ({
|
||||
id: item.dataset.id,
|
||||
image_path: item.querySelector('img').src,
|
||||
action: item.querySelector('.selection-action').classList[1].replace('action-', '')
|
||||
}));
|
||||
|
||||
document.querySelectorAll('.selection-item').forEach(item => item.classList.add('selected'));
|
||||
updateDownloadButton();
|
||||
|
||||
showToast(`Selected all ${checkboxes.length} visible images`);
|
||||
});
|
||||
|
||||
deselectAllBtn.addEventListener('click', () => {
|
||||
document.querySelectorAll('.selection-checkbox').forEach(cb => cb.checked = false);
|
||||
selectedItems = [];
|
||||
document.querySelectorAll('.selection-item').forEach(item => item.classList.remove('selected'));
|
||||
updateDownloadButton();
|
||||
|
||||
showToast('Deselected all images');
|
||||
});
|
||||
|
||||
// Enhanced download functionality
|
||||
downloadSelectedBtn.addEventListener('click', () => {
|
||||
if (selectedItems.length === 0) return;
|
||||
|
||||
// Show loading toast
|
||||
showToast(`Preparing ${selectedItems.length} images for download...`);
|
||||
|
||||
const paths = selectedItems.map(item => item.image_path);
|
||||
const query = paths.map(p => `paths=${encodeURIComponent(p)}`).join('&');
|
||||
|
||||
// Add a small delay to show the loading toast
|
||||
setTimeout(() => {
|
||||
window.location.href = `/download-selected?${query}`;
|
||||
}, 800);
|
||||
});
|
||||
|
||||
// Enhanced modal functionality
|
||||
closeActionModal.addEventListener('click', () => {
|
||||
actionModal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
actionModal.style.display = 'none';
|
||||
}, 400);
|
||||
});
|
||||
|
||||
actionButtons.forEach(button => {
|
||||
// Add ripple effect
|
||||
addRippleEffect(button);
|
||||
|
||||
button.addEventListener('click', function() {
|
||||
// Remove active class from all buttons
|
||||
actionButtons.forEach(btn => btn.classList.remove('active'));
|
||||
// Add active class to clicked button
|
||||
this.classList.add('active');
|
||||
|
||||
const action = this.dataset.action;
|
||||
|
||||
// Show feedback
|
||||
modalMessage.textContent = `Updating action to ${getActionName(action)}...`;
|
||||
modalMessage.style.color = '#3498db';
|
||||
|
||||
// Simulate API call (replace with actual implementation)
|
||||
setTimeout(() => {
|
||||
modalMessage.textContent = `Action updated successfully!`;
|
||||
modalMessage.style.color = '#2ecc71';
|
||||
|
||||
// Close modal after success
|
||||
setTimeout(() => {
|
||||
actionModal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
actionModal.style.display = 'none';
|
||||
modalMessage.textContent = '';
|
||||
}, 400);
|
||||
|
||||
// Update the UI to reflect the change
|
||||
const selectionItem = document.querySelector(`.selection-item[data-id="${currentSelectionId}"]`);
|
||||
if (selectionItem) {
|
||||
const actionEl = selectionItem.querySelector('.selection-action');
|
||||
const oldAction = actionEl.classList[1].replace('action-', '');
|
||||
|
||||
// Update the action element
|
||||
actionEl.className = `selection-action action-${action}`;
|
||||
actionEl.innerHTML = `<i class="fa-solid ${getActionIcon(action)}"></i> ${getActionName(action)}`;
|
||||
|
||||
// Update the selection in allSelections
|
||||
const selectionIndex = allSelections.findIndex(s => s.id === currentSelectionId);
|
||||
if (selectionIndex !== -1) {
|
||||
allSelections[selectionIndex].action = action;
|
||||
}
|
||||
|
||||
// Update stats
|
||||
const stats = calculateStats(allSelections);
|
||||
updateStats(stats);
|
||||
|
||||
// Show toast notification
|
||||
showToast(`Updated to ${getActionName(action)}`);
|
||||
}
|
||||
}, 1000);
|
||||
}, 800);
|
||||
});
|
||||
});
|
||||
|
||||
// Enhanced reset functionality
|
||||
resetBtn.addEventListener('click', () => {
|
||||
resetModal.style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
resetModal.classList.add('show');
|
||||
}, 10);
|
||||
});
|
||||
|
||||
confirmResetBtn.addEventListener('click', () => {
|
||||
// Show loading state
|
||||
confirmResetBtn.disabled = true;
|
||||
confirmResetBtn.textContent = 'Deleting...';
|
||||
resetMessage.textContent = 'Deleting all selections...';
|
||||
resetMessage.style.color = '#3498db';
|
||||
|
||||
// Simulate API call (replace with actual implementation)
|
||||
setTimeout(() => {
|
||||
resetMessage.textContent = 'All selections have been deleted successfully!';
|
||||
resetMessage.style.color = '#2ecc71';
|
||||
|
||||
// Close modal after success
|
||||
setTimeout(() => {
|
||||
resetModal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
resetModal.style.display = 'none';
|
||||
confirmResetBtn.disabled = false;
|
||||
confirmResetBtn.textContent = 'Yes, Delete All';
|
||||
resetMessage.textContent = '';
|
||||
|
||||
// Clear the grid and update state
|
||||
selectionGrid.innerHTML = `
|
||||
<div class="no-selections">
|
||||
<i class="fa-solid fa-image-slash fa-3x"></i>
|
||||
<p>No selections found</p>
|
||||
</div>
|
||||
`;
|
||||
selectedItems = [];
|
||||
allSelections = [];
|
||||
updateDownloadButton();
|
||||
|
||||
// Update stats
|
||||
const stats = calculateStats([]);
|
||||
updateStats(stats);
|
||||
|
||||
// Show toast notification
|
||||
showToast('All selections have been deleted');
|
||||
}, 400);
|
||||
}, 1000);
|
||||
}, 1500);
|
||||
});
|
||||
|
||||
cancelResetBtn.addEventListener('click', () => {
|
||||
resetModal.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
resetModal.style.display = 'none';
|
||||
}, 400);
|
||||
});
|
||||
|
||||
// Add filtering animation styles
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.selection-grid.filtering {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.98);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.no-selections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 50px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #3498db;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: white;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.checkbox-label:hover {
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.selection-checkbox {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.selection-checkbox:checked + .checkbox-label {
|
||||
background-color: #3498db;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.selection-checkbox:checked + .checkbox-label::after {
|
||||
content: '\\f00c';
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-weight: 900;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.confirmation-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.confirm-delete-btn {
|
||||
background: var(--gradient-danger);
|
||||
}
|
||||
|
||||
.cancel-delete-btn {
|
||||
background: var(--gradient-primary);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Initialize by loading selections
|
||||
loadSelections();
|
||||
});
|
||||
Reference in New Issue
Block a user