233 lines
8.5 KiB
JavaScript
233 lines
8.5 KiB
JavaScript
import { showToast, updateImageInfo } from './utils.js';
|
|
import SwipeCard from '../components/swipe-card.js';
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Track total images and processed count for progress bar
|
|
const progressState = {
|
|
totalImages: 0,
|
|
processedImages: 0
|
|
};
|
|
|
|
// Global state
|
|
const state = {
|
|
currentImageInfo: null,
|
|
currentOrientation: 'all',
|
|
isLoading: false
|
|
};
|
|
|
|
// DOM elements
|
|
const lastActionText = document.getElementById('last-action');
|
|
const orientationFilters = document.querySelector('.orientation-filters');
|
|
const modal = document.getElementById('fullscreen-modal');
|
|
const fullscreenImage = document.getElementById('fullscreen-image');
|
|
const closeModal = document.querySelector('.close-modal');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
|
|
// Initialize the enhanced swipe card
|
|
const swipeCard = new SwipeCard({
|
|
container: document.querySelector('.swipe-container'),
|
|
onSwipe: performSwipe,
|
|
threshold: 100
|
|
});
|
|
|
|
// Make state available to window for debugging and other components
|
|
window.state = state;
|
|
window.performSwipe = performSwipe;
|
|
|
|
function performSwipe(direction) {
|
|
if (!state.currentImageInfo) return;
|
|
|
|
// Update last action text with the action name instead of direction
|
|
const actionMap = {
|
|
left: 'Discarded',
|
|
right: 'Kept',
|
|
up: 'Favorited',
|
|
down: 'Marked for review'
|
|
};
|
|
lastActionText.textContent = `Last action: ${actionMap[direction] || 'Unknown'}`;
|
|
|
|
// Show toast notification
|
|
const toastMap = {
|
|
left: 'Discarded',
|
|
right: 'Kept',
|
|
up: 'Favorited',
|
|
down: 'Marked for review'
|
|
};
|
|
showToast(toastMap[direction] || 'Action');
|
|
|
|
// Record the selection
|
|
recordSelection(state.currentImageInfo, direction);
|
|
|
|
// Update progress
|
|
progressState.processedImages++;
|
|
updateProgressBar();
|
|
|
|
// Load new image after animation completes
|
|
setTimeout(() => {
|
|
loadNewImage();
|
|
}, 500);
|
|
}
|
|
|
|
function updateProgressBar() {
|
|
if (progressState.totalImages > 0) {
|
|
const percentage = (progressState.processedImages / progressState.totalImages) * 100;
|
|
progressBar.style.width = `${Math.min(percentage, 100)}%`;
|
|
}
|
|
}
|
|
|
|
function recordSelection(imageInfo, action) {
|
|
fetch('/selection', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
image_path: imageInfo.path,
|
|
resolution: imageInfo.resolution,
|
|
action,
|
|
}),
|
|
}).catch(error => console.error('Error recording selection:', error));
|
|
}
|
|
|
|
function loadNewImage() {
|
|
if (state.isLoading) return;
|
|
state.isLoading = true;
|
|
swipeCard.showLoading();
|
|
|
|
// First, get the total count if we don't have it yet
|
|
if (progressState.totalImages === 0) {
|
|
fetch('/image-count')
|
|
.then(response => response.json())
|
|
.catch(() => ({ count: 100 })) // Fallback if endpoint doesn't exist
|
|
.then(data => {
|
|
progressState.totalImages = data.count || 100;
|
|
updateProgressBar();
|
|
});
|
|
}
|
|
|
|
fetch(`/random-image?orientation=${state.currentOrientation}&t=${new Date().getTime()}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
state.isLoading = false;
|
|
swipeCard.hideLoading();
|
|
|
|
if (data && data.path) {
|
|
state.currentImageInfo = data;
|
|
swipeCard.setImage(data);
|
|
updateImageInfo(data);
|
|
adjustContainerToImage(data.orientation);
|
|
} else {
|
|
swipeCard.card.innerHTML = `<div class="no-images-message">${data.message || 'No more images.'}</div>`;
|
|
state.currentImageInfo = null;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching image:', error);
|
|
state.isLoading = false;
|
|
swipeCard.hideLoading();
|
|
swipeCard.card.innerHTML = '<div class="no-images-message">Error loading image.</div>';
|
|
});
|
|
}
|
|
|
|
function adjustContainerToImage(orientation) {
|
|
const container = document.querySelector('.swipe-container');
|
|
if (window.innerWidth < 992) { // Only on desktop
|
|
container.style.transition = 'all 0.5s ease-in-out';
|
|
if (orientation === 'landscape') {
|
|
container.style.flex = '4';
|
|
} else {
|
|
container.style.flex = '2';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Button event listeners
|
|
document.getElementById('btn-left').addEventListener('click', () => performSwipe('left'));
|
|
document.getElementById('btn-right').addEventListener('click', () => performSwipe('right'));
|
|
document.getElementById('btn-up').addEventListener('click', () => performSwipe('up'));
|
|
document.getElementById('btn-down').addEventListener('click', () => performSwipe('down'));
|
|
|
|
// Orientation filter event listeners
|
|
orientationFilters.addEventListener('click', (e) => {
|
|
if (e.target.tagName === 'BUTTON' && !e.target.classList.contains('active')) {
|
|
orientationFilters.querySelector('.active').classList.remove('active');
|
|
e.target.classList.add('active');
|
|
state.currentOrientation = e.target.dataset.orientation;
|
|
loadNewImage();
|
|
}
|
|
});
|
|
|
|
// Modal event listeners
|
|
swipeCard.card.addEventListener('click', () => {
|
|
if (!swipeCard.state.hasMoved && state.currentImageInfo) {
|
|
fullscreenImage.src = state.currentImageInfo.path;
|
|
document.getElementById('modal-resolution').textContent = `Resolution: ${state.currentImageInfo.resolution}`;
|
|
document.getElementById('modal-filename').textContent = `Filename: ${state.currentImageInfo.filename || 'N/A'}`;
|
|
document.getElementById('modal-creation-date').textContent = `Creation Date: ${state.currentImageInfo.creation_date || 'N/A'}`;
|
|
modal.style.display = 'flex';
|
|
|
|
// Add animation classes
|
|
setTimeout(() => {
|
|
modal.classList.add('show');
|
|
}, 10);
|
|
}
|
|
});
|
|
|
|
closeModal.addEventListener('click', () => {
|
|
modal.classList.remove('show');
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
}, 400);
|
|
});
|
|
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
modal.classList.remove('show');
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
}, 400);
|
|
}
|
|
});
|
|
|
|
// Keyboard event listeners
|
|
document.addEventListener('keydown', (e) => {
|
|
if (modal.style.display === 'flex' && e.key === 'Escape') {
|
|
modal.classList.remove('show');
|
|
setTimeout(() => {
|
|
modal.style.display = 'none';
|
|
}, 400);
|
|
return;
|
|
}
|
|
|
|
if (modal.style.display !== 'flex') {
|
|
switch (e.key) {
|
|
case 'ArrowLeft': performSwipe('left'); break;
|
|
case 'ArrowRight': performSwipe('right'); break;
|
|
case 'ArrowUp': performSwipe('up'); break;
|
|
case 'ArrowDown': performSwipe('down'); break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add ripple effect to action buttons
|
|
document.querySelectorAll('.action-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
const ripple = document.createElement('span');
|
|
ripple.classList.add('ripple');
|
|
this.appendChild(ripple);
|
|
|
|
const rect = button.getBoundingClientRect();
|
|
const size = Math.max(rect.width, rect.height);
|
|
|
|
ripple.style.width = ripple.style.height = `${size}px`;
|
|
ripple.style.left = `${e.clientX - rect.left - size/2}px`;
|
|
ripple.style.top = `${e.clientY - rect.top - size/2}px`;
|
|
|
|
setTimeout(() => {
|
|
ripple.remove();
|
|
}, 600);
|
|
});
|
|
});
|
|
|
|
// Initialize by loading the first image
|
|
loadNewImage();
|
|
});
|