Move file addition to modal
This commit is contained in:
@@ -1,154 +1,194 @@
|
||||
// Lazy loading for gallery images using Intersection Observer
|
||||
let currentOffset = 20; // Initial load was 20 items
|
||||
let isLoading = false;
|
||||
let hasMore = true;
|
||||
// Wait for DOM to be fully loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Upload form toggle functionality
|
||||
const addFilesBtn = document.getElementById('add-files-btn');
|
||||
const uploadFormCard = document.getElementById('upload-form-card');
|
||||
const closeUploadBtn = document.getElementById('close-upload-btn');
|
||||
|
||||
// Create intersection observer for lazy loading
|
||||
const loadingElement = document.getElementById('loading');
|
||||
const galleryElement = document.getElementById('gallery');
|
||||
if (addFilesBtn && uploadFormCard) {
|
||||
addFilesBtn.addEventListener('click', () => {
|
||||
uploadFormCard.style.display = 'block';
|
||||
addFilesBtn.classList.add('active');
|
||||
});
|
||||
|
||||
if (loadingElement && galleryElement) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !isLoading && hasMore) {
|
||||
loadMoreImages();
|
||||
if (closeUploadBtn) {
|
||||
closeUploadBtn.addEventListener('click', () => {
|
||||
uploadFormCard.style.display = 'none';
|
||||
addFilesBtn.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Close on Escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && uploadFormCard.style.display === 'block') {
|
||||
uploadFormCard.style.display = 'none';
|
||||
addFilesBtn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '200px' // Start loading 200px before the element comes into view
|
||||
});
|
||||
|
||||
observer.observe(loadingElement);
|
||||
}
|
||||
// Close when clicking outside the form
|
||||
document.addEventListener('click', (e) => {
|
||||
if (uploadFormCard.style.display === 'block' &&
|
||||
!uploadFormCard.contains(e.target) &&
|
||||
!addFilesBtn.contains(e.target)) {
|
||||
uploadFormCard.style.display = 'none';
|
||||
addFilesBtn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMoreImages() {
|
||||
if (isLoading || !hasMore) return;
|
||||
// Lazy loading for gallery images using Intersection Observer
|
||||
let currentOffset = 20; // Initial load was 20 items
|
||||
let isLoading = false;
|
||||
let hasMore = true;
|
||||
|
||||
isLoading = true;
|
||||
loadingElement.style.display = 'block';
|
||||
// Create intersection observer for lazy loading
|
||||
const loadingElement = document.getElementById('loading');
|
||||
const galleryElement = document.getElementById('gallery');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/media/load?offset=${currentOffset}&limit=20`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load images');
|
||||
}
|
||||
|
||||
const mediaItems = await response.json();
|
||||
|
||||
if (mediaItems.length === 0) {
|
||||
hasMore = false;
|
||||
loadingElement.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new images to gallery
|
||||
mediaItems.forEach(media => {
|
||||
const galleryItem = createGalleryItem(media);
|
||||
galleryElement.appendChild(galleryItem);
|
||||
if (loadingElement && galleryElement) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && !isLoading && hasMore) {
|
||||
loadMoreImages();
|
||||
}
|
||||
});
|
||||
}, {
|
||||
rootMargin: '200px' // Start loading 200px before the element comes into view
|
||||
});
|
||||
|
||||
currentOffset += mediaItems.length;
|
||||
} catch (error) {
|
||||
console.error('Error loading images:', error);
|
||||
loadingElement.innerHTML = 'Failed to load more images.';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
if (hasMore) {
|
||||
loadingElement.style.display = 'none';
|
||||
observer.observe(loadingElement);
|
||||
}
|
||||
|
||||
async function loadMoreImages() {
|
||||
if (isLoading || !hasMore) return;
|
||||
|
||||
isLoading = true;
|
||||
loadingElement.style.display = 'block';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/media/load?offset=${currentOffset}&limit=20`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load images');
|
||||
}
|
||||
|
||||
const mediaItems = await response.json();
|
||||
|
||||
if (mediaItems.length === 0) {
|
||||
hasMore = false;
|
||||
loadingElement.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new images to gallery
|
||||
mediaItems.forEach(media => {
|
||||
const galleryItem = createGalleryItem(media);
|
||||
galleryElement.appendChild(galleryItem);
|
||||
});
|
||||
|
||||
currentOffset += mediaItems.length;
|
||||
} catch (error) {
|
||||
console.error('Error loading images:', error);
|
||||
loadingElement.innerHTML = 'Failed to load more images.';
|
||||
} finally {
|
||||
isLoading = false;
|
||||
if (hasMore) {
|
||||
loadingElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createGalleryItem(media) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'gallery-item';
|
||||
item.setAttribute('data-media-id', media.id);
|
||||
function createGalleryItem(media) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'gallery-item';
|
||||
item.setAttribute('data-media-id', media.id);
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = `/api/media/image/${media.id}`;
|
||||
img.alt = media.fileName;
|
||||
img.loading = 'lazy';
|
||||
item.appendChild(img);
|
||||
const img = document.createElement('img');
|
||||
img.src = `/api/media/image/${media.id}`;
|
||||
img.alt = media.fileName;
|
||||
img.loading = 'lazy';
|
||||
item.appendChild(img);
|
||||
|
||||
if (media.description) {
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'gallery-item-description';
|
||||
desc.textContent = media.description;
|
||||
item.appendChild(desc);
|
||||
}
|
||||
|
||||
const info = document.createElement('div');
|
||||
info.className = 'gallery-item-info';
|
||||
|
||||
const date = document.createElement('span');
|
||||
date.className = 'gallery-item-date';
|
||||
const createdDate = new Date(media.createdAt);
|
||||
date.textContent = createdDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
info.appendChild(date);
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = '?handler=Delete'; // Relative to current page
|
||||
form.style.display = 'inline';
|
||||
|
||||
// Add anti-forgery token
|
||||
const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]');
|
||||
if (tokenInput) {
|
||||
const tokenClone = tokenInput.cloneNode(true);
|
||||
form.appendChild(tokenClone);
|
||||
}
|
||||
|
||||
const hiddenInput = document.createElement('input');
|
||||
hiddenInput.type = 'hidden';
|
||||
hiddenInput.name = 'MediaId'; // Match C# property name exactly
|
||||
hiddenInput.value = media.id;
|
||||
form.appendChild(hiddenInput);
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.type = 'submit';
|
||||
deleteBtn.className = 'btn-delete';
|
||||
deleteBtn.textContent = 'Delete';
|
||||
deleteBtn.onclick = function(e) {
|
||||
if (!confirm('Are you sure you want to delete this image?')) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
if (media.description) {
|
||||
const desc = document.createElement('div');
|
||||
desc.className = 'gallery-item-description';
|
||||
desc.textContent = media.description;
|
||||
item.appendChild(desc);
|
||||
}
|
||||
};
|
||||
form.appendChild(deleteBtn);
|
||||
|
||||
info.appendChild(form);
|
||||
item.appendChild(info);
|
||||
const info = document.createElement('div');
|
||||
info.className = 'gallery-item-info';
|
||||
|
||||
return item;
|
||||
}
|
||||
const date = document.createElement('span');
|
||||
date.className = 'gallery-item-date';
|
||||
const createdDate = new Date(media.createdAt);
|
||||
date.textContent = createdDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
info.appendChild(date);
|
||||
|
||||
// Optional: Add image preview on click
|
||||
galleryElement?.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'IMG') {
|
||||
const modal = createImageModal(e.target.src);
|
||||
document.body.appendChild(modal);
|
||||
const form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = '?handler=Delete'; // Relative to current page
|
||||
form.style.display = 'inline';
|
||||
|
||||
// Add anti-forgery token
|
||||
const tokenInput = document.querySelector('input[name="__RequestVerificationToken"]');
|
||||
if (tokenInput) {
|
||||
const tokenClone = tokenInput.cloneNode(true);
|
||||
form.appendChild(tokenClone);
|
||||
}
|
||||
|
||||
const hiddenInput = document.createElement('input');
|
||||
hiddenInput.type = 'hidden';
|
||||
hiddenInput.name = 'MediaId'; // Match C# property name exactly
|
||||
hiddenInput.value = media.id;
|
||||
form.appendChild(hiddenInput);
|
||||
|
||||
const deleteBtn = document.createElement('button');
|
||||
deleteBtn.type = 'submit';
|
||||
deleteBtn.className = 'btn-delete';
|
||||
deleteBtn.textContent = 'Delete';
|
||||
deleteBtn.onclick = function(e) {
|
||||
if (!confirm('Are you sure you want to delete this image?')) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
form.appendChild(deleteBtn);
|
||||
|
||||
info.appendChild(form);
|
||||
item.appendChild(info);
|
||||
|
||||
return item;
|
||||
}
|
||||
});
|
||||
|
||||
function createImageModal(imageSrc) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'image-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-content">
|
||||
<img src="${imageSrc}" alt="Full size image" />
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('modal-backdrop') ||
|
||||
e.target.classList.contains('modal-close') ||
|
||||
e.target.classList.contains('image-modal')) {
|
||||
modal.remove();
|
||||
// Optional: Add image preview on click
|
||||
galleryElement?.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'IMG') {
|
||||
const modal = createImageModal(e.target.src);
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
function createImageModal(imageSrc) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'image-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-backdrop"></div>
|
||||
<div class="modal-content">
|
||||
<img src="${imageSrc}" alt="Full size image" />
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.classList.contains('modal-backdrop') ||
|
||||
e.target.classList.contains('modal-close') ||
|
||||
e.target.classList.contains('image-modal')) {
|
||||
modal.remove();
|
||||
}
|
||||
});
|
||||
|
||||
return modal;
|
||||
}
|
||||
}); // End of DOMContentLoaded
|
||||
|
||||
Reference in New Issue
Block a user