diff --git a/main-site/admin/admin.css b/main-site/admin/admin.css
index c2b9e23..b6f6479 100644
--- a/main-site/admin/admin.css
+++ b/main-site/admin/admin.css
@@ -37,15 +37,43 @@
transform: translateY(-2px);
}
-/* Selected card: blue ring */
-.admin-gallery-grid .card:has(.select-photo-checkbox:checked) {
- box-shadow: 0 0 0 2.5px #2e7dbf inset, 0 6px 20px rgba(24, 40, 72, 0.1);
+/* Selected card: blue ring + tinted header */
+.admin-gallery-grid .card.is-selected {
+ box-shadow: 0 0 0 3px #2e7dbf inset, 0 6px 20px rgba(24, 40, 72, 0.1);
+}
+.admin-gallery-grid .card.is-selected .select-header {
+ background: rgba(46, 125, 191, 0.14) !important;
}
-/* Card top bar (checkbox + tag count row) */
-.admin-gallery-grid .card-content.py-2 {
+/* Card being swept over during drag */
+.admin-gallery-grid .card.drag-over {
+ box-shadow: 0 0 0 3px #48c78e inset;
+}
+
+/* Rubber-band selection rectangle */
+#drag-select-rect {
+ position: fixed;
+ border: 2px solid hsl(217, 71%, 53%);
+ background: rgba(72, 95, 199, 0.08);
+ border-radius: 4px;
+ pointer-events: none;
+ z-index: 1000;
+}
+
+/* Card top bar — now the full select target */
+.select-header {
+ cursor: pointer;
+ user-select: none;
background: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid #ebe5d2;
+ transition: background 0.1s;
+}
+.select-header:hover {
+ background: rgba(46, 125, 191, 0.07) !important;
+}
+.select-check {
+ font-size: 1.1rem;
+ line-height: 1;
}
/* Action buttons in card footer */
diff --git a/main-site/admin/admin.js b/main-site/admin/admin.js
index 97f1a49..002e5ef 100644
--- a/main-site/admin/admin.js
+++ b/main-site/admin/admin.js
@@ -272,44 +272,47 @@ document.addEventListener('DOMContentLoaded', () => {
manageGallery.innerHTML = '';
const filtered = getFilteredPhotos();
if (!filtered.length) {
- const message = query
- ? 'No photos match your search.'
- : 'No photos yet. Upload a photo to get started.';
+ const message = needsTaggingFilter
+ ? 'All photos are tagged!'
+ : (getFilteredPhotos().length === 0 && manageSearchInput?.value)
+ ? 'No photos match your search.'
+ : 'No photos yet. Upload a photo to get started.';
manageGallery.innerHTML = `
`;
return;
}
- filtered.forEach(photo => {
+ const cards = filtered.map(photo => {
const tagCount = Array.isArray(photo.tags) ? photo.tags.length : 0;
const tagStatusClass = tagCount <= 2 ? 'is-warning' : 'is-light';
const lowTagClass = tagCount <= 2 ? 'low-tag-card' : '';
+ const isSelected = selectedPhotoIds.has(photo._id);
const readableTags = (Array.isArray(photo.tags) ? photo.tags : []).map(displayTag);
- const photoCard = `
+ const imgSrc = photo.variants?.thumb ? `${backendUrl}/${photo.variants.thumb}` : `${backendUrl}/${photo.path}`;
+ return `
-
-
-
+
+
-
+
-
Caption: ${photo.caption}
-
Tags: ${readableTags.join(', ')}
+
Caption: ${photo.caption}
+
Tags: ${readableTags.join(', ')}
-
- `;
- manageGallery.innerHTML += photoCard;
+
`;
});
+ manageGallery.innerHTML = cards.join('');
}
function openEditModal(photoId) {
@@ -543,37 +546,84 @@ document.addEventListener('DOMContentLoaded', () => {
}
manageGallery.addEventListener('click', (e) => {
- if (e.target.classList.contains('edit-button')) {
+ if (dragMoved) return; // suppress click after a drag
+ const editBtn = e.target.closest('.edit-button');
+ const deleteBtn = e.target.closest('.delete-button');
+ const header = e.target.closest('.select-header');
+ if (editBtn) {
e.preventDefault();
- const photoId = e.target.closest('.card').dataset.photoId;
- openEditModal(photoId);
+ openEditModal(editBtn.closest('.card').dataset.photoId);
+ return;
}
- if (e.target.classList.contains('delete-button')) {
+ if (deleteBtn) {
e.preventDefault();
- const photoId = e.target.closest('.card').dataset.photoId;
- deletePhoto(photoId);
+ deletePhoto(deleteBtn.closest('.card').dataset.photoId);
+ return;
}
- if (e.target.classList.contains('select-photo-checkbox')) {
- const id = e.target.dataset.photoId;
- if (e.target.checked) {
- selectedPhotoIds.add(id);
- } else {
- selectedPhotoIds.delete(id);
- }
+ if (header) {
+ const id = header.dataset.photoId;
+ if (selectedPhotoIds.has(id)) selectedPhotoIds.delete(id);
+ else selectedPhotoIds.add(id);
+ renderManageGallery();
updateBulkUI();
}
});
- manageGallery.addEventListener('change', (e) => {
- if (e.target.classList.contains('select-photo-checkbox')) {
- const id = e.target.dataset.photoId;
- if (e.target.checked) {
- selectedPhotoIds.add(id);
- } else {
- selectedPhotoIds.delete(id);
+ // ── Drag-to-select ────────────────────────────────────────────────────────
+ let dragRect = null;
+ let dragOrigin = { x: 0, y: 0 };
+ let dragMoved = false;
+
+ manageGallery.addEventListener('mousedown', (e) => {
+ if (e.button !== 0) return;
+ if (e.target.closest('.card-footer-item') || e.target.closest('.card-image')) return;
+ dragOrigin = { x: e.clientX, y: e.clientY };
+ dragMoved = false;
+ dragRect = null;
+
+ const onMove = (ev) => {
+ const dx = ev.clientX - dragOrigin.x;
+ const dy = ev.clientY - dragOrigin.y;
+ if (!dragMoved && Math.abs(dx) < 6 && Math.abs(dy) < 6) return;
+ dragMoved = true;
+
+ if (!dragRect) {
+ dragRect = document.createElement('div');
+ dragRect.id = 'drag-select-rect';
+ document.body.appendChild(dragRect);
}
- updateBulkUI();
- }
+ const x = Math.min(ev.clientX, dragOrigin.x);
+ const y = Math.min(ev.clientY, dragOrigin.y);
+ const w = Math.abs(dx);
+ const h = Math.abs(dy);
+ Object.assign(dragRect.style, { left: x + 'px', top: y + 'px', width: w + 'px', height: h + 'px' });
+
+ const selBounds = { left: x, top: y, right: x + w, bottom: y + h };
+ manageGallery.querySelectorAll('[data-photo-id]').forEach(card => {
+ const b = card.getBoundingClientRect();
+ const hit = b.right > selBounds.left && b.left < selBounds.right &&
+ b.bottom > selBounds.top && b.top < selBounds.bottom;
+ card.classList.toggle('drag-over', hit);
+ });
+ };
+
+ const onUp = () => {
+ document.removeEventListener('mousemove', onMove);
+ document.removeEventListener('mouseup', onUp);
+ if (dragRect) { dragRect.remove(); dragRect = null; }
+ if (dragMoved) {
+ manageGallery.querySelectorAll('.card.drag-over').forEach(card => {
+ selectedPhotoIds.add(card.dataset.photoId);
+ card.classList.remove('drag-over');
+ });
+ renderManageGallery();
+ updateBulkUI();
+ }
+ setTimeout(() => { dragMoved = false; }, 0);
+ };
+
+ document.addEventListener('mousemove', onMove);
+ document.addEventListener('mouseup', onUp);
});
if (manageSearchInput) {