Basic UI
This commit is contained in:
857
history.html
857
history.html
@@ -5,325 +5,43 @@
|
||||
<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;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
padding: 8px 15px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
background-color: #f5f5f5;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.filter-section h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
background-color: #ddd;
|
||||
color: #333;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.filter-btn.active {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.resolution-select {
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
padding: 8px 15px;
|
||||
background-color: #ff4757;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select-btn {
|
||||
padding: 8px 15px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
padding: 8px 15px;
|
||||
background-color: #2ecc71;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.download-btn:disabled {
|
||||
background-color: #95a5a6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.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;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.selection-item.selected {
|
||||
box-shadow: 0 0 0 3px #3498db, 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.selection-checkbox-container {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.selection-checkbox {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 1000;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 10% auto;
|
||||
padding: 20px;
|
||||
width: 80%;
|
||||
max-width: 600px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.no-selections {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
color: #666;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-KfkFx7UiO/8VdM4DJ8GIzQ3pObu7q9gP/yu1ZPTM0u88Z+cIXtA8nKg9ePC60zY+XvKw5xpbIX8zahPszp5C8w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
<body>
|
||||
<!-- Action change modal -->
|
||||
<div id="action-modal" class="modal">
|
||||
<div class="modal-content" style="max-width: 400px; height: auto;">
|
||||
<div class="modal-content">
|
||||
<span class="close-modal" id="close-action-modal">×</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 id="modal-image-preview">
|
||||
<img id="modal-preview-img" src="" alt="Image preview">
|
||||
</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 class="action-buttons">
|
||||
<button class="action-btn" data-action="left">Discard</button>
|
||||
<button class="action-btn" data-action="right">Keep</button>
|
||||
<button class="action-btn" data-action="up">Favorite</button>
|
||||
<button class="action-btn" data-action="down">Review</button>
|
||||
</div>
|
||||
<div id="modal-message" style="text-align: center; margin-top: 10px; color: #666;"></div>
|
||||
<div id="modal-message"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="history-container">
|
||||
<div class="history-header">
|
||||
<h1>Image Selection History</h1>
|
||||
<div class="header-buttons">
|
||||
<a href="/" class="back-button">Back to Swipe</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 id="reset-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<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"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1 class="app-title"><i class="fa-solid fa-images"></i> History</h1>
|
||||
<a href="/" class="history-link">Back to Swipe</a>
|
||||
</header>
|
||||
|
||||
<div class="filter-container">
|
||||
<div class="filter-section">
|
||||
@@ -333,10 +51,9 @@
|
||||
<button class="filter-btn" data-filter="left">Discarded</button>
|
||||
<button class="filter-btn" data-filter="right">Kept</button>
|
||||
<button class="filter-btn" data-filter="up">Favorited</button>
|
||||
<button class="filter-btn" data-filter="down">Review Later</button>
|
||||
<button class="filter-btn" data-filter="down">Review</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<h4>Orientation</h4>
|
||||
<div class="filter-buttons orientation-filters">
|
||||
@@ -346,532 +63,26 @@
|
||||
<button class="filter-btn" data-orientation="square">Square</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<h4>Resolution</h4>
|
||||
<select id="resolution-filter" class="resolution-select">
|
||||
<option value="all">All Resolutions</option>
|
||||
<!-- This will be populated dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button id="reset-db" class="reset-btn">Reset Database</button>
|
||||
<button id="select-all" class="select-btn">Select All</button>
|
||||
<button id="deselect-all" class="select-btn">Deselect All</button>
|
||||
<button id="download-selected" class="download-btn" disabled>Download Selected</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button id="reset-db" class="action-btn reset-btn"><i class="fa-solid fa-trash"></i><span class="label">Reset</span></button>
|
||||
<button id="select-all" class="action-btn select-btn"><i class="fa-solid fa-check-double"></i><span class="label">Select All</span></button>
|
||||
<button id="deselect-all" class="action-btn select-btn"><i class="fa-regular fa-square"></i><span class="label">Deselect All</span></button>
|
||||
<button id="download-selected" class="action-btn download-btn" disabled><i class="fa-solid fa-download"></i><span class="label">Download</span></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() {
|
||||
// DOM elements
|
||||
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');
|
||||
|
||||
// Action modal elements
|
||||
const actionModal = document.getElementById('action-modal');
|
||||
const closeActionModal = document.getElementById('close-action-modal');
|
||||
const actionButtons = document.querySelectorAll('.action-btn');
|
||||
const modalPreviewImg = document.getElementById('modal-preview-img');
|
||||
const modalMessage = document.getElementById('modal-message');
|
||||
|
||||
// Reset modal elements
|
||||
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');
|
||||
|
||||
// State variables
|
||||
let currentFilter = 'all';
|
||||
let currentOrientation = 'all';
|
||||
let currentResolution = 'all';
|
||||
let selectedItems = [];
|
||||
let currentSelectionId = null;
|
||||
|
||||
// Load selections on page load
|
||||
loadSelections();
|
||||
|
||||
// Action filter button click handlers
|
||||
filterButtons.forEach(button => {
|
||||
if (button.dataset.filter) {
|
||||
button.addEventListener('click', function() {
|
||||
// Remove active class from all action filter buttons
|
||||
filterButtons.forEach(btn => {
|
||||
if (btn.dataset.filter) btn.classList.remove('active');
|
||||
});
|
||||
|
||||
// Add active class to clicked button
|
||||
this.classList.add('active');
|
||||
|
||||
// Update current filter
|
||||
currentFilter = this.dataset.filter;
|
||||
|
||||
// Reload selections with new filter
|
||||
loadSelections();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Orientation filter button click handlers
|
||||
orientationButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
// Remove active class from all orientation filter buttons
|
||||
orientationButtons.forEach(btn => btn.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked button
|
||||
this.classList.add('active');
|
||||
|
||||
// Update current orientation filter
|
||||
currentOrientation = this.dataset.orientation;
|
||||
|
||||
// Reload selections with new filter
|
||||
loadSelections();
|
||||
});
|
||||
});
|
||||
|
||||
// Resolution filter change handler
|
||||
resolutionFilter.addEventListener('change', function() {
|
||||
currentResolution = this.value;
|
||||
loadSelections();
|
||||
});
|
||||
|
||||
// Select All button handler
|
||||
selectAllBtn.addEventListener('click', function() {
|
||||
const checkboxes = document.querySelectorAll('.selection-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
const selectionItem = checkbox.closest('.selection-item');
|
||||
selectionItem.classList.add('selected');
|
||||
|
||||
// Add to selected items if not already there
|
||||
const id = selectionItem.dataset.id;
|
||||
if (!selectedItems.some(item => item.id === id)) {
|
||||
// Get the image path from the img element
|
||||
let imagePath = selectionItem.querySelector('img').src;
|
||||
// Convert absolute URL to relative path
|
||||
if (imagePath.includes('localhost')) {
|
||||
// Extract just the path portion from the full URL
|
||||
const url = new URL(imagePath);
|
||||
imagePath = url.pathname;
|
||||
}
|
||||
|
||||
const selection = {
|
||||
id: id,
|
||||
image_path: imagePath,
|
||||
action: selectionItem.dataset.action,
|
||||
resolution: selectionItem.dataset.resolution,
|
||||
orientation: selectionItem.dataset.orientation
|
||||
};
|
||||
selectedItems.push(selection);
|
||||
}
|
||||
});
|
||||
updateDownloadButton();
|
||||
});
|
||||
|
||||
// Deselect All button handler
|
||||
deselectAllBtn.addEventListener('click', function() {
|
||||
const checkboxes = document.querySelectorAll('.selection-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = false;
|
||||
checkbox.closest('.selection-item').classList.remove('selected');
|
||||
});
|
||||
selectedItems = [];
|
||||
updateDownloadButton();
|
||||
});
|
||||
|
||||
// Download Selected button handler
|
||||
downloadSelectedBtn.addEventListener('click', function() {
|
||||
if (selectedItems.length === 0) return;
|
||||
|
||||
// Create URL with image paths
|
||||
const paths = selectedItems.map(item => {
|
||||
let path = item.image_path;
|
||||
// Ensure path starts with /images/
|
||||
if (!path.startsWith('/images/')) {
|
||||
path = `/images/${path}`;
|
||||
}
|
||||
return path;
|
||||
});
|
||||
const queryString = paths.map(path => `paths=${encodeURIComponent(path)}`).join('&');
|
||||
const downloadUrl = `/download-selected?${queryString}`;
|
||||
|
||||
// Create a temporary link and click it to trigger download
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = 'selected_images.zip';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
|
||||
// Close action modal handler
|
||||
closeActionModal.addEventListener('click', function() {
|
||||
actionModal.style.display = 'none';
|
||||
});
|
||||
|
||||
// Action button click handlers
|
||||
actionButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const action = this.dataset.action;
|
||||
updateSelectionAction(currentSelectionId, action);
|
||||
});
|
||||
});
|
||||
|
||||
// Reset button click handler
|
||||
resetBtn.addEventListener('click', function() {
|
||||
resetModal.style.display = 'block';
|
||||
resetMessage.textContent = '';
|
||||
});
|
||||
|
||||
// Confirm reset button handler
|
||||
confirmResetBtn.addEventListener('click', function() {
|
||||
resetDatabase();
|
||||
});
|
||||
|
||||
// Cancel reset button handler
|
||||
cancelResetBtn.addEventListener('click', function() {
|
||||
resetModal.style.display = 'none';
|
||||
});
|
||||
|
||||
// Close modals when clicking outside
|
||||
window.addEventListener('click', function(event) {
|
||||
if (event.target === actionModal) {
|
||||
actionModal.style.display = 'none';
|
||||
}
|
||||
if (event.target === resetModal) {
|
||||
resetModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Function to load selections from the server
|
||||
function loadSelections() {
|
||||
console.log('DEBUG: loadSelections() called');
|
||||
selectionGrid.innerHTML = `<div class="no-selections">Loading selections...</div>`;
|
||||
|
||||
fetch('/selections')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server returned ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Selections data:', data);
|
||||
if (data.selections && data.selections.length > 0) {
|
||||
// Populate resolution filter
|
||||
populateResolutionFilter(data.selections);
|
||||
// Render the selections
|
||||
renderSelections(data.selections);
|
||||
} else {
|
||||
selectionGrid.innerHTML = '<div class="no-selections">No selections found</div>';
|
||||
downloadSelectedBtn.disabled = true;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading selections:', error);
|
||||
selectionGrid.innerHTML = `<div class="error">Error loading selections: ${error.message}</div>`;
|
||||
downloadSelectedBtn.disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to populate resolution filter
|
||||
function populateResolutionFilter(selections) {
|
||||
// Get unique resolutions
|
||||
const resolutions = [...new Set(selections.map(s => s.resolution))];
|
||||
resolutions.sort();
|
||||
|
||||
// Clear existing options except 'All'
|
||||
while (resolutionFilter.options.length > 1) {
|
||||
resolutionFilter.remove(1);
|
||||
}
|
||||
|
||||
// Add resolution options
|
||||
resolutions.forEach(resolution => {
|
||||
const option = document.createElement('option');
|
||||
option.value = resolution;
|
||||
option.textContent = resolution;
|
||||
resolutionFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to render selections
|
||||
function renderSelections(selections) {
|
||||
// Clear the grid
|
||||
selectionGrid.innerHTML = '';
|
||||
|
||||
// Apply all filters
|
||||
let filteredSelections = selections;
|
||||
|
||||
// Filter by action
|
||||
if (currentFilter !== 'all') {
|
||||
filteredSelections = filteredSelections.filter(s => s.action === currentFilter);
|
||||
}
|
||||
|
||||
// Filter by orientation
|
||||
if (currentOrientation !== 'all') {
|
||||
filteredSelections = filteredSelections.filter(s => s.orientation === currentOrientation);
|
||||
}
|
||||
|
||||
// Filter by resolution
|
||||
if (currentResolution !== 'all') {
|
||||
filteredSelections = filteredSelections.filter(s => s.resolution === currentResolution);
|
||||
}
|
||||
|
||||
if (filteredSelections.length === 0) {
|
||||
selectionGrid.innerHTML = '<div class="no-selections">No selections match the current filters</div>';
|
||||
downloadSelectedBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset selected items
|
||||
selectedItems = [];
|
||||
updateDownloadButton();
|
||||
|
||||
// Create and append selection items
|
||||
filteredSelections.forEach(selection => {
|
||||
const selectionItem = document.createElement('div');
|
||||
selectionItem.className = 'selection-item';
|
||||
selectionItem.dataset.id = selection.id;
|
||||
selectionItem.dataset.action = selection.action;
|
||||
selectionItem.dataset.orientation = selection.orientation || 'unknown';
|
||||
selectionItem.dataset.resolution = selection.resolution;
|
||||
|
||||
// Create checkbox container
|
||||
const checkboxContainer = document.createElement('div');
|
||||
checkboxContainer.className = 'selection-checkbox-container';
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.className = 'selection-checkbox';
|
||||
checkbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
selectionItem.classList.add('selected');
|
||||
selectedItems.push(selection);
|
||||
} else {
|
||||
selectionItem.classList.remove('selected');
|
||||
selectedItems = selectedItems.filter(item => item.id !== selection.id);
|
||||
}
|
||||
updateDownloadButton();
|
||||
});
|
||||
checkboxContainer.appendChild(checkbox);
|
||||
|
||||
// Create image container
|
||||
const imgContainer = document.createElement('div');
|
||||
imgContainer.className = 'selection-image-container';
|
||||
|
||||
// Create image
|
||||
const img = document.createElement('img');
|
||||
// Ensure image path starts with /images/
|
||||
let imagePath = selection.image_path;
|
||||
if (!imagePath.startsWith('/images/')) {
|
||||
imagePath = `/images/${imagePath}`;
|
||||
}
|
||||
img.src = imagePath;
|
||||
img.alt = 'Selected image';
|
||||
imgContainer.appendChild(img);
|
||||
|
||||
// Create action badge
|
||||
const actionBadge = document.createElement('div');
|
||||
actionBadge.className = `selection-action action-${selection.action}`;
|
||||
actionBadge.textContent = getActionName(selection.action);
|
||||
|
||||
// Create info container
|
||||
const infoContainer = document.createElement('div');
|
||||
infoContainer.className = 'selection-info';
|
||||
|
||||
// Add path, resolution, orientation and timestamp
|
||||
const pathText = document.createElement('p');
|
||||
pathText.textContent = selection.image_path.split('/').pop(); // Just show filename
|
||||
|
||||
const resolutionText = document.createElement('p');
|
||||
resolutionText.textContent = `Resolution: ${selection.resolution}`;
|
||||
|
||||
const orientationText = document.createElement('p');
|
||||
orientationText.textContent = `Orientation: ${selection.orientation || 'Unknown'}`;
|
||||
|
||||
const timestampText = document.createElement('p');
|
||||
const date = new Date(selection.timestamp * 1000);
|
||||
timestampText.textContent = `Date: ${date.toLocaleString()}`;
|
||||
|
||||
infoContainer.appendChild(pathText);
|
||||
infoContainer.appendChild(resolutionText);
|
||||
infoContainer.appendChild(orientationText);
|
||||
infoContainer.appendChild(timestampText);
|
||||
|
||||
// Create controls container
|
||||
const controlsContainer = document.createElement('div');
|
||||
controlsContainer.className = 'selection-controls';
|
||||
|
||||
// Create edit button
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'control-btn edit-btn';
|
||||
editBtn.textContent = 'Change';
|
||||
editBtn.addEventListener('click', function() {
|
||||
openActionModal(selection);
|
||||
});
|
||||
|
||||
// Create delete button
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.className = 'control-btn delete-btn';
|
||||
deleteBtn.textContent = 'Remove';
|
||||
deleteBtn.addEventListener('click', function() {
|
||||
deleteSelection(selection.id);
|
||||
});
|
||||
|
||||
controlsContainer.appendChild(editBtn);
|
||||
controlsContainer.appendChild(deleteBtn);
|
||||
|
||||
// Assemble the selection item
|
||||
selectionItem.appendChild(checkboxContainer);
|
||||
selectionItem.appendChild(imgContainer);
|
||||
selectionItem.appendChild(actionBadge);
|
||||
selectionItem.appendChild(infoContainer);
|
||||
selectionItem.appendChild(controlsContainer);
|
||||
|
||||
selectionGrid.appendChild(selectionItem);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update download button state
|
||||
function updateDownloadButton() {
|
||||
if (selectedItems.length > 0) {
|
||||
downloadSelectedBtn.disabled = false;
|
||||
downloadSelectedBtn.textContent = `Download Selected (${selectedItems.length})`;
|
||||
} else {
|
||||
downloadSelectedBtn.disabled = true;
|
||||
downloadSelectedBtn.textContent = 'Download Selected';
|
||||
}
|
||||
}
|
||||
|
||||
// Function to open the action change modal
|
||||
function openActionModal(selection) {
|
||||
console.log('DEBUG: Opening action modal for selection:', selection);
|
||||
currentSelectionId = selection.id;
|
||||
modalPreviewImg.src = selection.image_path;
|
||||
modalMessage.textContent = '';
|
||||
actionModal.style.display = 'block';
|
||||
}
|
||||
|
||||
// Function to update a selection's action
|
||||
function updateSelectionAction(id, action) {
|
||||
console.log(`DEBUG: Updating selection ${id} to action ${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 => {
|
||||
console.log('Selection updated:', data);
|
||||
modalMessage.textContent = 'Updated successfully!';
|
||||
|
||||
// Close the modal after a short delay
|
||||
setTimeout(() => {
|
||||
actionModal.style.display = 'none';
|
||||
loadSelections(); // Reload the selections
|
||||
}, 1000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating selection:', error);
|
||||
modalMessage.textContent = `Error: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to delete a selection
|
||||
function deleteSelection(id) {
|
||||
console.log('DEBUG: Deleting selection with ID:', id);
|
||||
|
||||
if (!confirm('Are you sure you want to delete this selection?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/delete-selection?id=${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete selection');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Selection deleted:', data);
|
||||
loadSelections(); // Reload the selections
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error deleting selection:', error);
|
||||
alert('Error deleting selection');
|
||||
});
|
||||
}
|
||||
|
||||
// Function to reset the database
|
||||
function resetDatabase() {
|
||||
console.log('DEBUG: Resetting database');
|
||||
resetMessage.textContent = 'Resetting...';
|
||||
|
||||
fetch('/reset-database', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to reset database');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Database reset:', data);
|
||||
resetMessage.textContent = 'Database reset successfully!';
|
||||
|
||||
// Close the modal after a short delay
|
||||
setTimeout(() => {
|
||||
resetModal.style.display = 'none';
|
||||
loadSelections(); // Reload the selections
|
||||
}, 1000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error resetting database:', error);
|
||||
resetMessage.textContent = `Error: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get the display name for an action
|
||||
function getActionName(action) {
|
||||
switch(action) {
|
||||
case 'left': return 'Discard';
|
||||
case 'right': return 'Keep';
|
||||
case 'up': return 'Favorite';
|
||||
case 'down': return 'Review';
|
||||
default: return action;
|
||||
}
|
||||
}
|
||||
|
||||
// Add console logging to help debug
|
||||
console.log('History page script loaded');
|
||||
});
|
||||
</script>
|
||||
<script src="js/history.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user