diff --git a/Media.JoshHeaps.Net/Pages/Index.cshtml b/Media.JoshHeaps.Net/Pages/Index.cshtml
index 72128d8..c74d66e 100644
--- a/Media.JoshHeaps.Net/Pages/Index.cshtml
+++ b/Media.JoshHeaps.Net/Pages/Index.cshtml
@@ -29,8 +29,11 @@
-
-
Upload Image
+
+
+
+
@section Scripts {
}
\ No newline at end of file
diff --git a/Media.JoshHeaps.Net/wwwroot/css/gallery.css b/Media.JoshHeaps.Net/wwwroot/css/gallery.css
index 0a0c53d..60467ac 100644
--- a/Media.JoshHeaps.Net/wwwroot/css/gallery.css
+++ b/Media.JoshHeaps.Net/wwwroot/css/gallery.css
@@ -1,3 +1,78 @@
+/* Floating Add Files Button */
+.floating-add-btn {
+ position: fixed;
+ bottom: 30px;
+ right: 30px;
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ border: none;
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+ cursor: pointer;
+ z-index: 1000;
+ transition: transform 0.2s, box-shadow 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32px;
+ line-height: 1;
+}
+
+.floating-add-btn:hover {
+ transform: scale(1.1);
+ box-shadow: 0 6px 16px rgba(102, 126, 234, 0.6);
+}
+
+.floating-add-btn:active {
+ transform: scale(0.95);
+}
+
+.floating-add-btn .btn-icon {
+ transition: transform 0.3s;
+}
+
+.floating-add-btn.active .btn-icon {
+ transform: rotate(45deg);
+}
+
+/* Upload Form Styles */
+#upload-form-card {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 1001;
+ max-width: 500px;
+ width: 90%;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+}
+
+.upload-form-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
+}
+
+.close-upload-form {
+ background: transparent;
+ border: none;
+ font-size: 32px;
+ color: #999;
+ cursor: pointer;
+ padding: 0;
+ width: 32px;
+ height: 32px;
+ line-height: 1;
+ transition: color 0.2s;
+}
+
+.close-upload-form:hover {
+ color: #333;
+}
+
/* Gallery Grid Layout */
.gallery-grid {
display: grid;
diff --git a/Media.JoshHeaps.Net/wwwroot/js/gallery.js b/Media.JoshHeaps.Net/wwwroot/js/gallery.js
index cd07c27..b878842 100644
--- a/Media.JoshHeaps.Net/wwwroot/js/gallery.js
+++ b/Media.JoshHeaps.Net/wwwroot/js/gallery.js
@@ -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 = `
-
-
-

-
-
- `;
-
- 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 = `
+
+
+

+
+
+ `;
+
+ 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