new gallery
This commit is contained in:
parent
444a4b9306
commit
68cba1f8a4
5 changed files with 264 additions and 2 deletions
124
camp/static/css/site.scss
Normal file
124
camp/static/css/site.scss
Normal file
|
@ -0,0 +1,124 @@
|
|||
.gallery-dialog {
|
||||
&:open {
|
||||
background: #282828;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
&:open::backdrop {
|
||||
background: black;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.photo-container {
|
||||
position: relative;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
.photo-container .image {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
@media screen and (max-width: 39.9375em) {
|
||||
.photo-container .image {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
.main-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.caption {
|
||||
margin: 10px 0;
|
||||
font-size: 18px;
|
||||
color: lightgray;
|
||||
text-align: center;
|
||||
}
|
||||
.thumbnails {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.thumbnail {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
.thumbnail.active {
|
||||
border-color: blue;
|
||||
}
|
||||
.nav-btn {
|
||||
cursor: pointer;
|
||||
font-size: 20px;
|
||||
padding: 10px;
|
||||
background: #007BFF;
|
||||
color: white;
|
||||
border: none;
|
||||
margin: 10px;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -16px;
|
||||
outline: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-indent: -9999px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.close {
|
||||
color: lightgray !important;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 3px;
|
||||
font-size: 20px;
|
||||
}
|
||||
.close:hover {
|
||||
color: white !important;
|
||||
}
|
||||
.download.disabled {
|
||||
display: none;
|
||||
}
|
||||
.download {
|
||||
color: lightgray !important;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 24px
|
||||
|
||||
}
|
||||
.download:hover {
|
||||
color: white !important;
|
||||
}
|
||||
@media screen and (max-width: 39.9375em) {
|
||||
.close {
|
||||
bottom: 6px;
|
||||
top: initial;
|
||||
}
|
||||
.download {
|
||||
bottom: 2px;
|
||||
top: initial;
|
||||
}
|
||||
}
|
||||
.nav-btn.next {
|
||||
right: 0px;
|
||||
background: url('/static/css/controls.png') no-repeat -43px -32px;
|
||||
}
|
||||
.nav-btn.prev {
|
||||
left: 0px;
|
||||
background: url('/static/css/controls.png') no-repeat 0 -32px;
|
||||
}
|
||||
.nav-btn:disabled {
|
||||
display: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
130
camp/static/js/gallery.js
Normal file
130
camp/static/js/gallery.js
Normal file
|
@ -0,0 +1,130 @@
|
|||
function loadGalleries() {
|
||||
document.querySelectorAll('.gallery').forEach(gallery => {
|
||||
var photos = [], dialog
|
||||
gallery.querySelectorAll(".photo").forEach(img => {
|
||||
const src = img.src.replace('_thumbnail', '_display')
|
||||
let photo = {
|
||||
src: src,
|
||||
caption: img.dataset.caption,
|
||||
orig: img.dataset.orig
|
||||
}
|
||||
photos.push(photo)
|
||||
img.parentElement.addEventListener("click", event => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
dialog.open(src)
|
||||
})
|
||||
})
|
||||
dialog = loadGallery(photos)
|
||||
gallery.appendChild(dialog.element)
|
||||
})
|
||||
}
|
||||
|
||||
function loadGallery(images) {
|
||||
var gallery = document.createElement("dialog")
|
||||
gallery.classList.add("gallery-dialog")
|
||||
gallery.innerHTML = `
|
||||
<div class="photo-container">
|
||||
<div class="image">
|
||||
<img class="main-image" src="" alt="">
|
||||
</div>
|
||||
<button class="prev nav-btn">❮ Prev</button>
|
||||
<button class="next nav-btn">Next ❯</button>
|
||||
</div>
|
||||
<button class="close">✖</button>
|
||||
<a class="download disabled" target="_blank" download title="download original">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 304 384"><path fill="currentColor" d="M299 128L149 277L0 128h85V0h128v128h86zM0 320h299v43H0v-43z"/></svg>
|
||||
</a>
|
||||
<div class="caption"></div>
|
||||
<div class="thumbnails"></div>
|
||||
`
|
||||
|
||||
gallery.addEventListener("click", event => {
|
||||
if (event.target.className == "close") {
|
||||
gallery.close();
|
||||
return
|
||||
}
|
||||
var rect = gallery.getBoundingClientRect();
|
||||
var isInDialog = (rect.top <= event.clientY && event.clientY <= rect.top + rect.height &&
|
||||
rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
|
||||
if (!isInDialog) {
|
||||
gallery.close();
|
||||
}
|
||||
})
|
||||
let currentIndex = 0;
|
||||
|
||||
const mainImage = gallery.querySelector(".main-image");
|
||||
const caption = gallery.querySelector(".caption");
|
||||
const prevBtn = gallery.querySelector(".prev.nav-btn");
|
||||
const nextBtn = gallery.querySelector(".next.nav-btn");
|
||||
const thumbnailsContainer = gallery.querySelector(".thumbnails");
|
||||
const download = gallery.querySelector(".download");
|
||||
|
||||
function updateSlideshow(index) {
|
||||
mainImage.src = images[index].src.replace('_display', '_thumbnail');
|
||||
setTimeout(() => {
|
||||
mainImage.src = images[index].src;
|
||||
}, 0)
|
||||
caption.innerHTML = images[index].caption;
|
||||
gallery.querySelectorAll(".thumbnail").forEach((thumb, i) => {
|
||||
thumb.classList.toggle("active", i === index);
|
||||
});
|
||||
if (images[index].orig) {
|
||||
download.href = images[index].orig
|
||||
download.classList.remove('disabled')
|
||||
} else {
|
||||
download.href = ""
|
||||
download.classList.add('disabled')
|
||||
}
|
||||
prevBtn.disabled = index === 0;
|
||||
nextBtn.disabled = index === images.length - 1;
|
||||
}
|
||||
|
||||
function createThumbnails() {
|
||||
images.forEach((img, index) => {
|
||||
const thumb = document.createElement("img");
|
||||
thumb.src = img.src.replace('_display', '_thumbnail');
|
||||
thumb.classList.add("thumbnail");
|
||||
thumb.onclick = () => {
|
||||
currentIndex = index;
|
||||
updateSlideshow(index);
|
||||
};
|
||||
thumbnailsContainer.appendChild(thumb);
|
||||
});
|
||||
}
|
||||
|
||||
function showSlideshow() {
|
||||
window.dialog = gallery
|
||||
gallery.showModal()
|
||||
}
|
||||
|
||||
prevBtn.onclick = () => {
|
||||
if (currentIndex > 0) {
|
||||
currentIndex--;
|
||||
updateSlideshow(currentIndex);
|
||||
}
|
||||
};
|
||||
|
||||
nextBtn.onclick = () => {
|
||||
if (currentIndex < images.length - 1) {
|
||||
currentIndex++;
|
||||
updateSlideshow(currentIndex);
|
||||
}
|
||||
};
|
||||
|
||||
createThumbnails();
|
||||
updateSlideshow(currentIndex);
|
||||
|
||||
const imagesIndex = images.map(img => img.src);
|
||||
return {
|
||||
element: gallery,
|
||||
open: function(src) {
|
||||
currentIndex = imagesIndex.indexOf(src)
|
||||
updateSlideshow(currentIndex)
|
||||
showSlideshow()
|
||||
}
|
||||
}
|
||||
}
|
||||
window.addEventListener("load", loadGalleries)
|
||||
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
<script src="{% static "js/app.js" %}"></script>
|
||||
<script src="{% static "js/what-input.js" %}"></script>
|
||||
<script src="{% static "js/jquery.bxslider.js" %}?2"></script>
|
||||
<script src="{% static "js/gallery.js" %}"></script>
|
||||
<script src="{% static "js/slideshow.js" %}"></script>
|
||||
{% endcompress %}
|
||||
{% block end %}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% if gallery %}
|
||||
<h6><strong>{{gallery.title}}</strong></h6>
|
||||
<ul class="clearing-thumbs" data-clearing>
|
||||
<ul class="clearing-thumbs gallery" data-clearing>
|
||||
{% for photo in gallery.public %}
|
||||
<li><a href="{{ gallery.get_absolute_url }}#{{forloop.counter}}"><img src="{{ photo.get_thumbnail_url }}"></a></li>
|
||||
<li><a href="{{ gallery.get_absolute_url }}#{{forloop.counter}}"><img src="{{ photo.get_thumbnail_url }}" class="photo" data-caption="{{ photo.caption_html }}"{% if request.user.is_staff %} data-orig="{{ photo.image.url }}"{% endif %}></a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -26,6 +26,8 @@ from django.utils.timezone import now
|
|||
from django.utils.translation import gettext_lazy as _
|
||||
from PIL import Image, ImageEnhance, ImageFile, ImageFilter
|
||||
from sortedm2m.fields import SortedManyToManyField
|
||||
from markdownx.utils import markdownify
|
||||
|
||||
|
||||
from .managers import GalleryQuerySet, PhotoQuerySet
|
||||
from .utils.reflection import add_reflection
|
||||
|
@ -589,6 +591,11 @@ class Photo(ImageModel):
|
|||
matched = True
|
||||
return None
|
||||
|
||||
def caption_html(self):
|
||||
caption = self.caption
|
||||
if caption:
|
||||
return mark_safe(markdownify(caption))
|
||||
return caption
|
||||
|
||||
class BaseEffect(models.Model):
|
||||
name = models.CharField(_('name'),
|
||||
|
|
Loading…
Add table
Reference in a new issue