Compare commits

..

6 commits

Author SHA1 Message Date
j
b0d3281149 link to image in gallery 2025-03-25 22:46:08 +00:00
j
68cba1f8a4 new gallery 2025-03-25 22:34:55 +00:00
j
444a4b9306 use compress move js 2025-03-25 22:33:38 +00:00
j
d4543634e0 enable compress 2025-03-25 22:30:47 +00:00
j
aec089b4dd add sass support 2025-03-25 17:09:27 +00:00
j
6a3e63a2ff remove outdated app-namespaces 2025-03-25 14:04:04 +00:00
11 changed files with 415 additions and 113 deletions

View file

@ -20,6 +20,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
COMPRESS_ENABLED = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
@ -35,6 +36,9 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites', 'django.contrib.sites',
'compressor',
'sass_processor',
'braces', 'braces',
'django_extensions', 'django_extensions',
'markdownx', 'markdownx',
@ -76,7 +80,6 @@ TEMPLATES = [
'django.template.context_processors.media', 'django.template.context_processors.media',
], ],
'loaders': [ 'loaders': [
'app_namespace.Loader',
'django.template.loaders.filesystem.Loader', 'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader', 'django.template.loaders.app_directories.Loader',
], ],
@ -141,6 +144,12 @@ SITE_ROOT = os.path.dirname(os.path.realpath(__file__))
# https://docs.djangoproject.com/en/1.11/howto/static-files/ # https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
"sass_processor.finders.CssFinder",
"compressor.finders.CompressorFinder",
)
MARKDOWNX_MEDIA_PATH = 'images/markdown' MARKDOWNX_MEDIA_PATH = 'images/markdown'
MARKDOWNX_EDITOR_RESIZABLE = True MARKDOWNX_EDITOR_RESIZABLE = True

124
camp/static/css/site.scss Normal file
View 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;
}
}

View file

@ -15,3 +15,40 @@ function getCookie(name) {
} }
return cookieValue; return cookieValue;
} }
var restrictedElements = [];
function removeBehaviorsRestrictions(event) {
if (restrictedElements.length > 0) {
var rElements = restrictedElements;
restrictedElements = [];
rElements.forEach(function(video) {
if (video.autoplay && video.paused) {
video.load()
video.play()
if (video.paused) {
restrictedElements.push(video)
}
} else if (video.readyState < 4) {
video.load()
}
});
}
}
if (document.querySelector('video')) {
let video = document.createElement('video');
video.muted = true
video.play();
if (video.paused) {
restrictedElements = document.querySelectorAll('video.gif')
window.addEventListener('keydown', removeBehaviorsRestrictions);
window.addEventListener('mousedown', removeBehaviorsRestrictions);
window.addEventListener('touchstart', removeBehaviorsRestrictions);
restrictedElements.forEach(video => {
if (!video.poster) {
video.poster = video.src.replace('.mp4', '.jpg')
video.muted = true
}
})
}
}

154
camp/static/js/gallery.js Normal file
View file

@ -0,0 +1,154 @@
function parseGalleryHash() {
let parts = document.location.hash.slice(1).split('/')
if (parts[0] == 'g') {
return {
"gallery": parseInt(parts[1], 10),
"image": parseInt(parts[2], 10),
}
}
return {}
}
function loadGalleries() {
let current = parseGalleryHash()
let galleryIdx = 1
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, galleryIdx)
gallery.appendChild(dialog.element)
if (current.gallery == galleryIdx) {
dialog.open(undefined, current.image)
}
galleryIdx += 1
})
}
function loadGallery(images, idx) {
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">&#10006;</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") {
document.location.hash = ''
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) {
document.location.hash = ''
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 updateGallery(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;
document.location.hash = `#g/${idx}/${index}`
}
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;
updateGallery(index);
};
thumbnailsContainer.appendChild(thumb);
});
}
function showGallery() {
window.dialog = gallery
gallery.showModal()
}
prevBtn.onclick = () => {
if (currentIndex > 0) {
currentIndex--;
updateGallery(currentIndex);
}
};
nextBtn.onclick = () => {
if (currentIndex < images.length - 1) {
currentIndex++;
updateGallery(currentIndex);
}
};
createThumbnails();
updateGallery(currentIndex);
const imagesIndex = images.map(img => img.src);
return {
element: gallery,
open: function(src, idx=-1) {
if (idx != -1) {
currentIndex = idx
} else {
currentIndex = imagesIndex.indexOf(src)
}
updateGallery(currentIndex)
showGallery()
}
}
}
window.addEventListener("load", loadGalleries)

View file

@ -0,0 +1,65 @@
var startSlide = 0;
if (document.querySelectorAll('.select-image').length && document.location.hash.length) {
startSlide = parseInt(document.location.hash.slice(1)) - 1
}
function loadSlideshow() {
$('a.original-link').bind('touchstart MSPointerDown pointerdown', function(event) {
event.stopPropagation()
});
$('a.original-link').on({
mousedown: function(event) {
event.stopPropagation()
},
})
slider = $('.slider1').show().bxSlider({
startSlide: startSlide,
preloadImages: 'all',
adaptiveHeight : true,
pager : false,
});
var images = $('.slider1 img'), count = images.length;
images.on({load: function(event) {
setTimeout(function() {
slider.resize()
slider.redrawSlider()
}, 100)
}})
document.querySelectorAll('.select-image').forEach(function(a) {
a.onclick = function(event) {
event.preventDefault();
slider.goToSlide(parseInt(this.dataset.id) - 1)
document.location.hash = '#' + this.dataset.id;
};
});
/*
if (document.querySelectorAll('.select-image').length && document.location.hash.length) {
var slide = parseInt(document.location.hash.slice(1))
if (slide) {
slider.goToSlide(slide - 1)
slider.resize()
slider.redrawSlider()
setTimeout(function() {
slider.resize()
slider.redrawSlider()
}, 2000)
}
}
*/
$(document).keydown(function(e){
if (e.keyCode == 39) // Right arrow
{
slider && slider.goToNextSlide();
return false;
}
else if (e.keyCode == 37) // left arrow
{
slider && slider.goToPrevSlide();
return false;
}
});
}
$(document).ready(loadSlideshow);

View file

@ -1,4 +1,4 @@
{% load available_content static %} {% load available_content static compress sass_tags %}
<!doctype html> <!doctype html>
<html class="no-js" lang="en"> <html class="no-js" lang="en">
<head> <head>
@ -11,8 +11,11 @@
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans"/> <link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans"/>
<link rel="stylesheet" type="text/css" href="{% static "css/foundation.css" %}?20250201"> <link rel="stylesheet" type="text/css" href="{% static "css/foundation.css" %}?20250201">
<link rel="stylesheet" type="text/css" href="{% static "css/jquery.bxslider.css" %}"> <link rel="stylesheet" type="text/css" href="{% static "css/jquery.bxslider.css" %}">
{% compress css file site %}
<link rel="stylesheet" type="text/css" href="{% static "css/main.css" %}"> <link rel="stylesheet" type="text/css" href="{% static "css/main.css" %}">
<link rel="stylesheet" type="text/css" href="{% static "css/app.css" %}"> <link rel="stylesheet" type="text/css" href="{% static "css/app.css" %}">
<link rel="stylesheet" href="{% sass_src 'css/site.scss' %}"></link>
{% endcompress %}
</head> </head>
<body> <body>
@ -45,115 +48,16 @@
{% endblock %} {% endblock %}
{% load static %} {}
<script src="{% static "js/jquery.js" %}"></script> <script src="{% static "js/jquery.js" %}"></script>
<script src="{% static "js/foundation.js" %}"></script> <script src="{% static "js/foundation.js" %}"></script>
{% compress js file base %}
<script src="{% static "js/app.js" %}"></script> <script src="{% static "js/app.js" %}"></script>
<script src="{% static "js/what-input.js" %}"></script> <script src="{% static "js/what-input.js" %}"></script>
<script src="{% static "js/jquery.bxslider.js" %}?2"></script> <script src="{% static "js/jquery.bxslider.js" %}?2"></script>
<script type="text/javascript"> <script src="{% static "js/gallery.js" %}"></script>
var startSlide = 0; <script src="{% static "js/slideshow.js" %}"></script>
if (document.querySelectorAll('.select-image').length && document.location.hash.length) { {% endcompress %}
startSlide = parseInt(document.location.hash.slice(1)) - 1
}
function loadSlideshow() {
$('a.original-link').bind('touchstart MSPointerDown pointerdown', function(event) {
event.stopPropagation()
});
$('a.original-link').on({
mousedown: function(event) {
event.stopPropagation()
},
})
slider = $('.slider1').show().bxSlider({
startSlide: startSlide,
preloadImages: 'all',
adaptiveHeight : true,
pager : false,
});
var images = $('.slider1 img'), count = images.length;
images.on({load: function(event) {
setTimeout(function() {
slider.resize()
slider.redrawSlider()
}, 100)
}})
document.querySelectorAll('.select-image').forEach(function(a) {
a.onclick = function(event) {
event.preventDefault();
slider.goToSlide(parseInt(this.dataset.id) - 1)
document.location.hash = '#' + this.dataset.id;
};
});
/*
if (document.querySelectorAll('.select-image').length && document.location.hash.length) {
var slide = parseInt(document.location.hash.slice(1))
if (slide) {
slider.goToSlide(slide - 1)
slider.resize()
slider.redrawSlider()
setTimeout(function() {
slider.resize()
slider.redrawSlider()
}, 2000)
}
}
*/
$(document).keydown(function(e){
if (e.keyCode == 39) // Right arrow
{
slider && slider.goToNextSlide();
return false;
}
else if (e.keyCode == 37) // left arrow
{
slider && slider.goToPrevSlide();
return false;
}
});
}
$(document).ready(loadSlideshow);
var restrictedElements = [];
function removeBehaviorsRestrictions(event) {
if (restrictedElements.length > 0) {
var rElements = restrictedElements;
restrictedElements = [];
rElements.forEach(function(video) {
if (video.autoplay && video.paused) {
video.load()
video.play()
if (video.paused) {
restrictedElements.push(video)
}
} else if (video.readyState < 4) {
video.load()
}
});
}
}
if (document.querySelector('video')) {
let video = document.createElement('video');
video.muted = true
video.play();
if (video.paused) {
restrictedElements = document.querySelectorAll('video.gif')
window.addEventListener('keydown', removeBehaviorsRestrictions);
window.addEventListener('mousedown', removeBehaviorsRestrictions);
window.addEventListener('touchstart', removeBehaviorsRestrictions);
restrictedElements.forEach(video => {
if (!video.poster) {
video.poster = video.src.replace('.mp4', '.jpg')
video.muted = true
}
})
}
}
</script>
{% block end %} {% block end %}
{% endblock %} {% endblock %}

View file

@ -1,8 +1,8 @@
{% if gallery %} {% if gallery %}
<h6><strong>{{gallery.title}}</strong></h6> <h6><strong>{{gallery.title}}</strong></h6>
<ul class="clearing-thumbs" data-clearing> <ul class="clearing-thumbs gallery" data-clearing>
{% for photo in gallery.public %} {% 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 %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View file

@ -1,4 +1,4 @@
{% extends "photologue:photologue/gallery_detail.html" %} {% extends "photologue/gallery_detail.html" %}
{% load i18n %} {% load i18n %}
{% block title %}{{ gallery.title }}{% endblock %} {% block title %}{{ gallery.title }}{% endblock %}
@ -17,7 +17,7 @@
<p> <p>
{{ photo.caption|safe }} {{ photo.caption|safe }}
{% if photo.caption %}<br>{%endif%} {% if photo.caption %}<br>{%endif%}
<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a> {% if request.user.is_staff %}<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a>{% endif %}
</p> </p>
</li> </li>

View file

@ -1,4 +1,4 @@
{% extends "photologue:photologue/photo_detail.html" %} {% extends "photologue/photo_detail.html" %}
{% load photologue_tags i18n %} {% load photologue_tags i18n %}
{% block title %}{{ object.title }}{% endblock %} {% block title %}{{ object.title }}{% endblock %}
@ -10,7 +10,7 @@
<p> <p>
{{ object.caption|safe }} {{ object.caption|safe }}
{% if object.caption %}<br>{%endif%} {% if object.caption %}<br>{%endif%}
<a href="{{ object.image.url }}" class="original-link" target="_blank" >Link to original file</a> {% if request.user.is_staff %}<a href="{{ photo.image.url }}" class="original-link" target="_blank" >Link to original file</a>{% endif %}
</p> </p>
<ul> <ul>
{% for gallery in object.public_galleries %} {% for gallery in object.public_galleries %}

View file

@ -26,6 +26,8 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from PIL import Image, ImageEnhance, ImageFile, ImageFilter from PIL import Image, ImageEnhance, ImageFile, ImageFilter
from sortedm2m.fields import SortedManyToManyField from sortedm2m.fields import SortedManyToManyField
from markdownx.utils import markdownify
from .managers import GalleryQuerySet, PhotoQuerySet from .managers import GalleryQuerySet, PhotoQuerySet
from .utils.reflection import add_reflection from .utils.reflection import add_reflection
@ -589,6 +591,11 @@ class Photo(ImageModel):
matched = True matched = True
return None return None
def caption_html(self):
caption = self.caption
if caption:
return mark_safe(markdownify(caption))
return caption
class BaseEffect(models.Model): class BaseEffect(models.Model):
name = models.CharField(_('name'), name = models.CharField(_('name'),

View file

@ -1,7 +1,6 @@
django<5.0 django<5.0
django-markdownx django-markdownx
django-braces django-braces
django-app-namespace-template-loader
ox ox
gunicorn gunicorn
django_extensions django_extensions
@ -10,3 +9,6 @@ lxml
ExifRead>=3 ExifRead>=3
django-sortedm2m>=3.1.1 django-sortedm2m>=3.1.1
Pillow>=9 Pillow>=9
libsass
django-compressor
django-sass-processor