Files
swiper/history.html

629 lines
24 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Selection History</title>
<link rel="stylesheet" href="styles.css">
<style>
.history-container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header-buttons {
display: flex;
gap: 10px;
}
.reset-button {
padding: 8px 15px;
background-color: #ff4757;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
.reset-button:hover {
background-color: #ff6b81;
}
.reset-modal-buttons {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.danger-button {
padding: 10px 20px;
background-color: #ff4757;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.cancel-button {
padding: 10px 20px;
background-color: #ddd;
color: #333;
border: none;
border-radius: 5px;
cursor: pointer;
}
.back-button {
padding: 8px 15px;
background-color: #333;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
}
.selection-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.selection-item {
border-radius: 10px;
overflow: hidden;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
background-color: white;
position: relative;
}
.selection-item img {
width: 100%;
height: 200px;
object-fit: contain;
display: block;
}
.selection-info {
padding: 10px;
font-size: 0.9rem;
}
.selection-action {
position: absolute;
top: 10px;
right: 10px;
padding: 5px 10px;
border-radius: 20px;
color: white;
font-weight: bold;
}
.selection-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: space-around;
padding: 8px 0;
opacity: 0;
transition: opacity 0.3s;
}
.selection-item:hover .selection-controls {
opacity: 1;
}
.control-btn {
background: none;
border: none;
color: white;
cursor: pointer;
font-size: 0.8rem;
padding: 3px 8px;
border-radius: 3px;
transition: background-color 0.2s;
}
.control-btn:hover {
background-color: rgba(255, 255, 255, 0.2);
}
.edit-btn {
color: #1e90ff;
}
.delete-btn {
color: #ff4757;
}
.action-left {
background-color: #ff4757;
}
.action-right {
background-color: #2ed573;
}
.action-up {
background-color: #1e90ff;
}
.action-down {
background-color: #ffa502;
}
.filter-controls {
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.filter-button {
padding: 8px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
opacity: 0.7;
transition: opacity 0.3s;
}
.filter-button.active {
opacity: 1;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.filter-all {
background-color: #ddd;
color: #333;
}
.filter-left {
background-color: #ff4757;
color: white;
}
.filter-right {
background-color: #2ed573;
color: white;
}
.filter-up {
background-color: #1e90ff;
color: white;
}
.filter-down {
background-color: #ffa502;
color: white;
}
.no-selections {
text-align: center;
padding: 30px;
background-color: #f5f5f5;
border-radius: 10px;
color: #666;
}
.timestamp {
color: #777;
font-size: 0.8rem;
}
</style>
</head>
<body>
<!-- Action change modal -->
<div id="action-modal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 400px; height: auto;">
<span class="close-modal" id="close-action-modal">&times;</span>
<h2>Change Action</h2>
<div id="modal-image-preview" style="margin: 15px 0; text-align: center;">
<img id="modal-preview-img" src="" alt="Image preview" style="max-height: 200px; max-width: 100%;">
</div>
<div class="action-buttons" style="margin: 20px 0;">
<button class="action-btn" data-action="left" style="background-color: #ff4757;">Discard</button>
<button class="action-btn" data-action="right" style="background-color: #2ed573;">Keep</button>
<button class="action-btn" data-action="up" style="background-color: #1e90ff;">Favorite</button>
<button class="action-btn" data-action="down" style="background-color: #ffa502;">Review</button>
</div>
<div id="modal-message" style="text-align: center; margin-top: 10px; color: #666;"></div>
</div>
</div>
<div class="history-container">
<div class="history-header">
<h1>Image Selection History</h1>
<div class="header-buttons">
<button id="reset-button" class="reset-button">Reset Database</button>
<a href="/" class="back-button">Back to Swiper</a>
</div>
</div>
<!-- Reset confirmation modal -->
<div id="reset-modal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 400px; height: auto;">
<h2>Reset Database</h2>
<p>Are you sure you want to delete all selections? This cannot be undone.</p>
<div class="reset-modal-buttons">
<button id="confirm-reset" class="danger-button">Yes, Delete All</button>
<button id="cancel-reset" class="cancel-button">Cancel</button>
</div>
<div id="reset-message" style="text-align: center; margin-top: 15px; color: #666;"></div>
</div>
</div>
<div class="filter-controls">
<button class="filter-button filter-all active" data-filter="all">All</button>
<button class="filter-button filter-left" data-filter="left">Discarded</button>
<button class="filter-button filter-right" data-filter="right">Kept</button>
<button class="filter-button filter-up" data-filter="up">Favorited</button>
<button class="filter-button filter-down" data-filter="down">Review Later</button>
</div>
<div id="selection-grid" class="selection-grid">
<!-- Selection items will be loaded here dynamically -->
<div class="no-selections">Loading selections...</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const selectionGrid = document.getElementById('selection-grid');
const filterButtons = document.querySelectorAll('.filter-button');
let currentFilter = 'all';
let allSelections = [];
// Load selections from the server
loadSelections();
// Add event listeners to filter buttons
filterButtons.forEach(button => {
button.addEventListener('click', function() {
// Update active button
filterButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// Apply filter
currentFilter = this.dataset.filter;
renderSelections();
});
});
function loadSelections() {
console.log('DEBUG: loadSelections() called');
selectionGrid.innerHTML = `<div class="no-selections">Loading selections...</div>`;
fetch('/selections')
.then(response => {
console.log('DEBUG: Fetch response received:', response.status, response.statusText);
if (!response.ok) {
throw new Error(`Failed to fetch selections: ${response.status} ${response.statusText}`);
}
return response.text().then(text => {
console.log('DEBUG: Raw response text:', text.substring(0, 200) + '...');
try {
return JSON.parse(text);
} catch (e) {
console.error('DEBUG: JSON parse error:', e);
throw new Error(`Invalid JSON response: ${e.message}`);
}
});
})
.then(data => {
console.log('DEBUG: Data received:', data);
console.log('DEBUG: Selections count:', data.selections ? data.selections.length : 'undefined');
if (!data.selections) {
throw new Error('No selections array in response');
}
// Fix: Ensure each selection has an id property as a number
allSelections = data.selections.map(selection => {
// Make sure id is a number
if (selection.id) {
selection.id = parseInt(selection.id);
}
return selection;
});
console.log('DEBUG: First selection (if any):', allSelections.length > 0 ? allSelections[0] : 'none');
renderSelections();
})
.catch(error => {
console.error('DEBUG ERROR in loadSelections():', error);
selectionGrid.innerHTML = `
<div class="no-selections">
Error loading selections: ${error.message}<br>
<button onclick="loadSelections()" style="margin-top: 10px;">Try Again</button>
</div>
`;
});
}
// Variables for the action change modal
const actionModal = document.getElementById('action-modal');
const closeActionModal = document.getElementById('close-action-modal');
const modalPreviewImg = document.getElementById('modal-preview-img');
const modalMessage = document.getElementById('modal-message');
const actionButtons = actionModal.querySelectorAll('.action-btn');
let currentSelectionId = null;
// Variables for the reset modal
const resetButton = document.getElementById('reset-button');
const resetModal = document.getElementById('reset-modal');
const confirmResetButton = document.getElementById('confirm-reset');
const cancelResetButton = document.getElementById('cancel-reset');
const resetMessage = document.getElementById('reset-message');
// Close the action modal when clicking the close button
closeActionModal.addEventListener('click', function() {
actionModal.style.display = 'none';
});
// Close the action modal when clicking outside the content
window.addEventListener('click', function(e) {
if (e.target === actionModal) {
actionModal.style.display = 'none';
}
if (e.target === resetModal) {
resetModal.style.display = 'none';
}
});
// Reset button click handler
resetButton.addEventListener('click', function() {
resetModal.style.display = 'block';
resetMessage.textContent = '';
});
// Cancel reset button click handler
cancelResetButton.addEventListener('click', function() {
resetModal.style.display = 'none';
});
// Confirm reset button click handler
confirmResetButton.addEventListener('click', function() {
resetDatabase();
});
// Function to reset the database
function resetDatabase() {
resetMessage.textContent = 'Resetting database...';
confirmResetButton.disabled = true;
cancelResetButton.disabled = true;
fetch('/reset-database', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to reset database');
}
return response.json();
})
.then(data => {
resetMessage.textContent = data.message || 'Database reset successfully!';
// Clear the selections array
allSelections = [];
// Re-render the selections
renderSelections();
// Re-enable buttons after a delay
setTimeout(() => {
confirmResetButton.disabled = false;
cancelResetButton.disabled = false;
// Close the modal after a short delay
setTimeout(() => {
resetModal.style.display = 'none';
}, 1000);
}, 1000);
})
.catch(error => {
console.error('Error resetting database:', error);
resetMessage.textContent = `Error: ${error.message}`;
confirmResetButton.disabled = false;
cancelResetButton.disabled = false;
});
}
// Handle action button clicks in the modal
actionButtons.forEach(button => {
button.addEventListener('click', function() {
const action = this.dataset.action;
updateSelectionAction(currentSelectionId, action);
});
});
// Function to update a selection's action
function updateSelectionAction(id, action) {
modalMessage.textContent = 'Updating...';
fetch('/update-selection', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: id,
action: action
})
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to update selection');
}
return response.json();
})
.then(data => {
modalMessage.textContent = 'Updated successfully!';
// Update the selection in our local data
const selection = allSelections.find(s => s.id === id);
if (selection) {
selection.action = action;
selection.timestamp = Math.floor(Date.now() / 1000);
}
// Re-render the selections
renderSelections();
// Close the modal after a short delay
setTimeout(() => {
actionModal.style.display = 'none';
}, 1000);
})
.catch(error => {
console.error('Error updating selection:', error);
modalMessage.textContent = 'Error updating selection';
});
}
// Function to delete a selection
function deleteSelection(id) {
if (!confirm('Are you sure you want to delete this selection?')) {
return;
}
fetch('/delete-selection', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: id
})
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to delete selection');
}
return response.json();
})
.then(data => {
// Remove the selection from our local data
allSelections = allSelections.filter(s => s.id !== id);
// Re-render the selections
renderSelections();
})
.catch(error => {
console.error('Error deleting selection:', error);
alert('Error deleting selection');
});
}
function renderSelections() {
console.log('DEBUG: renderSelections called with filter:', currentFilter);
console.log('DEBUG: Total selections:', allSelections.length);
// Filter selections based on current filter
const filteredSelections = currentFilter === 'all'
? allSelections
: allSelections.filter(s => s.action === currentFilter);
console.log('DEBUG: Filtered selections count:', filteredSelections.length);
// Clear the grid
selectionGrid.innerHTML = '';
// Show message if no selections
if (filteredSelections.length === 0) {
selectionGrid.innerHTML = `
<div class="no-selections">
No ${currentFilter === 'all' ? '' : currentFilter + ' '} selections found.
</div>
`;
return;
}
// Render each selection
filteredSelections.forEach(selection => {
const date = new Date(selection.timestamp * 1000);
const formattedDate = date.toLocaleString();
const selectionItem = document.createElement('div');
selectionItem.className = 'selection-item';
selectionItem.innerHTML = `
<div class="selection-action action-${selection.action}">${getActionName(selection.action)}</div>
<img src="/images/${selection.image_path}" alt="Selected image">
<div class="selection-info">
<div>Resolution: ${selection.resolution}</div>
<div class="timestamp">${formattedDate}</div>
</div>
<div class="selection-controls">
<button class="control-btn edit-btn" data-id="${selection.id}">Change Action</button>
<button class="control-btn delete-btn" data-id="${selection.id}">Remove</button>
</div>
`;
// Add click event to view full image
selectionItem.querySelector('img').addEventListener('click', function() {
window.open(`/images/${selection.image_path}`, '_blank');
});
// Add event listeners for the control buttons
const editBtn = selectionItem.querySelector('.edit-btn');
const deleteBtn = selectionItem.querySelector('.delete-btn');
editBtn.addEventListener('click', function(e) {
e.stopPropagation();
const id = parseInt(this.dataset.id);
openActionModal(selection);
});
deleteBtn.addEventListener('click', function(e) {
e.stopPropagation();
const id = parseInt(this.dataset.id);
deleteSelection(id);
});
selectionGrid.appendChild(selectionItem);
});
}
// Function to open the action change modal
function openActionModal(selection) {
console.log('DEBUG: Opening action modal for selection:', selection);
currentSelectionId = selection.id;
modalPreviewImg.src = `/images/${selection.image_path}`;
modalMessage.textContent = '';
actionModal.style.display = 'block';
}
function getActionName(action) {
switch(action) {
case 'left': return 'Discard';
case 'right': return 'Keep';
case 'up': return 'Favorite';
case 'down': return 'Review';
default: return action;
}
}
});
</script>
</body>
</html>