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

${message}

`; 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 `
-
-
- +
+
+ + + ${tagCount} tag${tagCount === 1 ? '' : 's'}${tagCount <= 2 ? ' • add more' : ''}
- ${photo.caption} + ${photo.caption}
-

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) {