Browse Source

add caption

pull/528/head
Simon Conseil 1 year ago
parent
commit
3e747edfa9
  1. 29
      src/sigal/themes/default/templates/description.html
  2. 24
      src/sigal/themes/galleria/templates/album_items.html
  3. 414
      src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.js
  4. 5
      src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.min.js
  5. 53
      src/sigal/themes/photoswipe/static/styles.css
  6. 9
      src/sigal/themes/photoswipe/templates/album.html

29
src/sigal/themes/default/templates/description.html

@ -0,0 +1,29 @@
{% macro img_description(media) -%}
{%- if media.big -%}
<a href='{{ media.big_url }}'>Full size</a>
{%- endif -%}
{%- if media.description -%}
<br>{{ media.description }}
{%- endif -%}
{%- if media.exif -%}
<div class='film-meta'>
{%- if media.exif.iso -%}
<abbr title='film speed'>{{ media.exif.iso }}</abbr> {%- endif -%}
{%- if media.exif.exposure -%}
<abbr title='exposure'>{{ media.exif.exposure }}</abbr> {%- endif -%}
{%- if media.exif.fstop -%}
<abbr title='aperture'>{{ media.exif.fstop }}</abbr> {%- endif -%}
{%- if media.exif.focal -%}
<abbr title='focal length'>{{ media.exif.focal }}</abbr> {%- endif -%}
</div>
{%- if media.exif.gps -%}
<a title='location' href='https://www.openstreetmap.org/?mlat={{ media.exif.gps.lat }}&amp;mlon={{ media.exif.gps.lon}}&amp;zoom=12&amp;layers=M' target='_blank' class='map' >{{ 'N{:.6f}'.format(media.exif.gps.lat) if media.exif.gps.lat > 0 else 'S{:.6f}'.format(-media.exif.gps.lat) }}{{ 'E{:.6f}'.format(media.exif.gps.lon) if media.exif.gps.lon > 0 else 'W{:.6f}'.format(-media.exif.gps.lon) }}</a>
{%- endif -%}
{%- if media.exif.Make or media.exif.Model -%}
<abbr title='camera make and model'>{{ media.exif.Make }} {{ media.exif.Model }}</abbr>
{%- endif -%}
{%- if media.exif.datetime -%}
<abbr title='date'>{{ media.exif.datetime }}</abbr>
{%- endif -%}
{% endif %}
{%- endmacro %}

24
src/sigal/themes/galleria/templates/album_items.html

@ -1,3 +1,4 @@
{% from 'description.html' import img_description %}
<div class="icons">
<a id="fullscreen"><img src="{{ theme.url }}/img/fullscreen.png"
title="Fullscreen" alt="Fullscreen (f)" /></a>
@ -17,29 +18,6 @@
{% block late_js %}
{% if album.medias %}
{% macro img_description(media) -%}
{%- if media.big -%}<a href='{{ media.big_url }}'>Full size</a>{%- endif -%}
{# clean up tags and whitespace, including newlines, in the description #}
{%- if media.description -%}<br>{{ media.description }}{%- endif -%}
{%- if media.exif -%}
<div class='film-meta'>
{%- if media.exif.iso -%}<abbr title='film speed'>{{ media.exif.iso }}</abbr> {%- endif -%}
{%- if media.exif.exposure -%}<abbr title='exposure'>{{ media.exif.exposure }}</abbr> {%- endif -%}
{%- if media.exif.fstop -%}<abbr title='aperture'>{{ media.exif.fstop }}</abbr> {%- endif -%}
{%- if media.exif.focal -%}<abbr title='focal length'>{{ media.exif.focal }}</abbr> {%- endif -%}
</div>
{%- if media.exif.gps -%}
<a title='location' href='https://www.openstreetmap.org/?mlat={{ media.exif.gps.lat }}&amp;mlon={{ media.exif.gps.lon}}&amp;zoom=12&amp;layers=M' target='_blank' class='map' >{{ 'N{:.6f}'.format(media.exif.gps.lat) if media.exif.gps.lat > 0 else 'S{:.6f}'.format(-media.exif.gps.lat) }}{{ 'E{:.6f}'.format(media.exif.gps.lon) if media.exif.gps.lon > 0 else 'W{:.6f}'.format(-media.exif.gps.lon) }}</a>
{%- endif -%}
{%- if media.exif.Make or media.exif.Model -%}
<abbr title='camera make and model'>{{ media.exif.Make }} {{ media.exif.Model }}</abbr>
{%- endif -%}
{%- if media.exif.datetime -%}
<abbr title='date'>{{ media.exif.datetime }}</abbr>
{%- endif -%}
{% endif %}
{%- endmacro %}
<script src="{{ theme.url }}/jquery-3.3.1.min.js"></script>
<script src="{{ theme.url }}/galleria.min.js"></script>
<script src="{{ theme.url }}/themes/{{ settings.galleria_theme }}/galleria.{{ settings.galleria_theme }}.min.js"></script>

414
src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.js

@ -0,0 +1,414 @@
/**
* PhotoSwipe Dynamic Caption plugin v1.2.7
* https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin
*
* By https://dimsemenov.com
*/
const defaultOptions = {
captionContent: '.pswp-caption-content',
type: 'auto',
horizontalEdgeThreshold: 20,
mobileCaptionOverlapRatio: 0.3,
mobileLayoutBreakpoint: 600,
verticallyCenterImage: false
};
class PhotoSwipeDynamicCaption {
constructor(lightbox, options) {
this.options = {
...defaultOptions,
...options
};
this.lightbox = lightbox;
this.lightbox.on('init', () => {
this.pswp = this.lightbox.pswp;
this.initCaption();
});
}
initCaption() {
const { pswp } = this;
pswp.on('change', () => {
// make sure caption is displayed after slides are switched
this.showCaption(this.pswp.currSlide);
});
pswp.on('calcSlideSize', (e) => this.onCalcSlideSize(e));
pswp.on('slideDestroy', (e) => {
if (e.slide.dynamicCaption) {
if (e.slide.dynamicCaption.element) {
e.slide.dynamicCaption.element.remove();
}
delete e.slide.dynamicCaption;
}
});
// hide caption if zoomed
pswp.on('zoomPanUpdate', ({ slide }) => {
if (pswp.opener.isOpen && slide.dynamicCaption) {
if (slide.currZoomLevel > slide.zoomLevels.initial) {
this.hideCaption(slide);
} else {
this.showCaption(slide);
}
// move caption on vertical drag
if (slide.dynamicCaption.element) {
let captionYOffset = 0;
if (slide.currZoomLevel <= slide.zoomLevels.initial) {
const shiftedAmount = slide.pan.y - slide.bounds.center.y;
if (Math.abs(shiftedAmount) > 1) {
captionYOffset = shiftedAmount;
}
}
this.setCaptionYOffset(slide.dynamicCaption.element, captionYOffset);
}
this.adjustPanArea(slide, slide.currZoomLevel);
}
});
pswp.on('beforeZoomTo', (e) => {
this.adjustPanArea(pswp.currSlide, e.destZoomLevel);
});
// Stop default action of tap when tapping on the caption
pswp.on('tapAction', (e) => {
if (e.originalEvent.target.closest('.pswp__dynamic-caption')) {
e.preventDefault();
}
});
}
adjustPanArea(slide, zoomLevel) {
if (slide.dynamicCaption && slide.dynamicCaption.adjustedPanAreaSize) {
if (zoomLevel > slide.zoomLevels.initial) {
slide.panAreaSize.x = slide.dynamicCaption.originalPanAreaSize.x;
slide.panAreaSize.y = slide.dynamicCaption.originalPanAreaSize.y;
} else {
// Restore panAreaSize after we zoom back to initial position
slide.panAreaSize.x = slide.dynamicCaption.adjustedPanAreaSize.x;
slide.panAreaSize.y = slide.dynamicCaption.adjustedPanAreaSize.y;
}
}
}
useMobileLayout() {
const { mobileLayoutBreakpoint } = this.options;
if (typeof mobileLayoutBreakpoint === 'function') {
return mobileLayoutBreakpoint.call(this);
} else if (typeof mobileLayoutBreakpoint === 'number') {
if (window.innerWidth < mobileLayoutBreakpoint) {
return true;
}
}
return false;
}
hideCaption(slide) {
if (slide.dynamicCaption && !slide.dynamicCaption.hidden) {
const captionElement = slide.dynamicCaption.element;
if (!captionElement) {
return;
}
slide.dynamicCaption.hidden = true;
captionElement.classList.add('pswp__dynamic-caption--faded');
// Disable caption visibility with the delay, so it's not interactable
if (slide.captionFadeTimeout) {
clearTimeout(slide.captionFadeTimeout);
}
slide.captionFadeTimeout = setTimeout(() => {
captionElement.style.visibility = 'hidden';
delete slide.captionFadeTimeout;
}, 400);
}
}
setCaptionYOffset(el, y) {
el.style.transform = `translateY(${y}px)`;
}
showCaption(slide) {
if (slide.dynamicCaption && slide.dynamicCaption.hidden) {
const captionElement = slide.dynamicCaption.element;
if (!captionElement) {
return;
}
slide.dynamicCaption.hidden = false;
captionElement.style.visibility = 'visible';
clearTimeout(slide.captionFadeTimeout);
slide.captionFadeTimeout = setTimeout(() => {
captionElement.classList.remove('pswp__dynamic-caption--faded');
delete slide.captionFadeTimeout;;
}, 50);
}
}
setCaptionPosition(captionEl, x, y) {
const isOnHorizontalEdge = (x <= this.options.horizontalEdgeThreshold);
captionEl.classList[
isOnHorizontalEdge ? 'add' : 'remove'
]('pswp__dynamic-caption--on-hor-edge');
captionEl.style.left = x + 'px';
captionEl.style.top = y + 'px';
}
setCaptionWidth(captionEl, width) {
if (!width) {
captionEl.style.removeProperty('width');
} else {
captionEl.style.width = width + 'px';
}
}
setCaptionType(captionEl, type) {
const prevType = captionEl.dataset.pswpCaptionType;
if (type !== prevType) {
captionEl.classList.add('pswp__dynamic-caption--' + type);
captionEl.classList.remove('pswp__dynamic-caption--' + prevType);
captionEl.dataset.pswpCaptionType = type;
}
}
updateCaptionPosition(slide) {
if (!slide.dynamicCaption || !slide.dynamicCaption.type || !slide.dynamicCaption.element) {
return;
}
if (slide.dynamicCaption.type === 'mobile') {
this.setCaptionType(
slide.dynamicCaption.element,
slide.dynamicCaption.type
);
slide.dynamicCaption.element.style.removeProperty('left');
slide.dynamicCaption.element.style.removeProperty('top');
this.setCaptionWidth(slide.dynamicCaption.element, false);
return;
}
const zoomLevel = slide.zoomLevels.initial;
const imageWidth = Math.ceil(slide.width * zoomLevel);
const imageHeight = Math.ceil(slide.height * zoomLevel);
this.setCaptionType(slide.dynamicCaption.element, slide.dynamicCaption.type);
if (slide.dynamicCaption.type === 'aside') {
this.setCaptionPosition(
slide.dynamicCaption.element,
slide.bounds.center.x + imageWidth,
slide.bounds.center.y
);
this.setCaptionWidth(slide.dynamicCaption.element, false);
} else if (slide.dynamicCaption.type === 'below') {
this.setCaptionPosition(
slide.dynamicCaption.element,
slide.bounds.center.x,
slide.bounds.center.y + imageHeight
);
this.setCaptionWidth(slide.dynamicCaption.element, imageWidth);
}
}
onCalcSlideSize(e) {
const { slide } = e;
let captionSize;
let useMobileVersion;
if (!slide.dynamicCaption) {
slide.dynamicCaption = {
element: undefined,
type: false,
hidden: false
};
const captionHTML = this.getCaptionHTML(slide);
if (!captionHTML) {
return;
}
slide.dynamicCaption.element = document.createElement('div');
slide.dynamicCaption.element.className = 'pswp__dynamic-caption pswp__hide-on-close';
slide.dynamicCaption.element.innerHTML = captionHTML;
this.pswp.dispatch('dynamicCaptionUpdateHTML', {
captionElement: slide.dynamicCaption.element,
slide
});
slide.holderElement.appendChild(slide.dynamicCaption.element);
}
if (!slide.dynamicCaption.element) {
return;
}
this.storeOriginalPanAreaSize(slide);
slide.bounds.update(slide.zoomLevels.initial);
if (this.useMobileLayout()) {
slide.dynamicCaption.type = 'mobile';
useMobileVersion = true;
} else {
if (this.options.type === 'auto') {
if (slide.bounds.center.x > slide.bounds.center.y) {
slide.dynamicCaption.type = 'aside';
} else {
slide.dynamicCaption.type = 'below';
}
} else {
slide.dynamicCaption.type = this.options.type;
}
}
const imageWidth = Math.ceil(slide.width * slide.zoomLevels.initial);
const imageHeight = Math.ceil(slide.height * slide.zoomLevels.initial);
this.setCaptionType(
slide.dynamicCaption.element,
slide.dynamicCaption.type
);
if (slide.dynamicCaption.type === 'aside') {
this.setCaptionWidth(slide.dynamicCaption.element, false);
captionSize = this.measureCaptionSize(slide.dynamicCaption.element, e.slide);
const captionWidth = captionSize.x;
const horizontalEnding = imageWidth + slide.bounds.center.x;
const horizontalLeftover = (slide.panAreaSize.x - horizontalEnding);
if (horizontalLeftover <= captionWidth) {
slide.panAreaSize.x -= captionWidth;
this.recalculateZoomLevelAndBounds(slide);
} else {
// do nothing, caption will fit aside without any adjustments
}
} else if (slide.dynamicCaption.type === 'below' || useMobileVersion) {
this.setCaptionWidth(
slide.dynamicCaption.element,
useMobileVersion ? this.pswp.viewportSize.x : imageWidth
);
captionSize = this.measureCaptionSize(slide.dynamicCaption.element, e.slide);
const captionHeight = captionSize.y;
if (this.options.verticallyCenterImage) {
slide.panAreaSize.y -= captionHeight;
this.recalculateZoomLevelAndBounds(slide);
} else {
// Lift up the image only by caption height
// vertical ending of the image
const verticalEnding = imageHeight + slide.bounds.center.y;
// height between bottom of the screen and ending of the image
// (before any adjustments applied)
const verticalLeftover = slide.panAreaSize.y - verticalEnding;
const initialPanAreaHeight = slide.panAreaSize.y;
if (verticalLeftover <= captionHeight) {
// lift up the image to give more space for caption
slide.panAreaSize.y -= Math.min((captionHeight - verticalLeftover) * 2, captionHeight);
// we reduce viewport size, thus we need to update zoom level and pan bounds
this.recalculateZoomLevelAndBounds(slide);
const maxPositionX = slide.panAreaSize.x * this.options.mobileCaptionOverlapRatio / 2;
// Do not reduce viewport height if too few space available
if (useMobileVersion
&& slide.bounds.center.x > maxPositionX) {
// Restore the default position
slide.panAreaSize.y = initialPanAreaHeight;
this.recalculateZoomLevelAndBounds(slide);
}
}
}
} else {
// mobile
}
this.storeAdjustedPanAreaSize(slide);
this.updateCaptionPosition(slide);
}
measureCaptionSize(captionEl, slide) {
const rect = captionEl.getBoundingClientRect();
const event = this.pswp.dispatch('dynamicCaptionMeasureSize', {
captionEl,
slide,
captionSize: {
x: rect.width,
y: rect.height
}
});
return event.captionSize;
}
recalculateZoomLevelAndBounds(slide) {
slide.zoomLevels.update(slide.width, slide.height, slide.panAreaSize);
slide.bounds.update(slide.zoomLevels.initial);
}
storeAdjustedPanAreaSize(slide) {
if (slide.dynamicCaption) {
if (!slide.dynamicCaption.adjustedPanAreaSize) {
slide.dynamicCaption.adjustedPanAreaSize = {};
}
slide.dynamicCaption.adjustedPanAreaSize.x = slide.panAreaSize.x;
slide.dynamicCaption.adjustedPanAreaSize.y = slide.panAreaSize.y;
}
}
storeOriginalPanAreaSize(slide) {
if (slide.dynamicCaption) {
if (!slide.dynamicCaption.originalPanAreaSize) {
slide.dynamicCaption.originalPanAreaSize = {};
}
slide.dynamicCaption.originalPanAreaSize.x = slide.panAreaSize.x;
slide.dynamicCaption.originalPanAreaSize.y = slide.panAreaSize.y;
}
}
getCaptionHTML(slide) {
if (typeof this.options.captionContent === 'function') {
return this.options.captionContent.call(this, slide);
}
const currSlideElement = slide.data.element;
let captionHTML = '';
if (currSlideElement) {
const hiddenCaption = currSlideElement.querySelector(this.options.captionContent);
if (hiddenCaption) {
// get caption from element with class pswp-caption-content
captionHTML = hiddenCaption.innerHTML;
} else {
const img = currSlideElement.querySelector('img');
if (img) {
// get caption from alt attribute
captionHTML = img.getAttribute('alt');
}
}
}
return captionHTML;
}
}
export default PhotoSwipeDynamicCaption;

5
src/sigal/themes/photoswipe/static/photoswipe-dynamic-caption-plugin.esm.min.js vendored

File diff suppressed because one or more lines are too long

53
src/sigal/themes/photoswipe/static/styles.css

@ -233,3 +233,56 @@ footer span:not(:last-child):after {
width: 100%;
}
}
.pswp__dynamic-caption {
color: #fff;
position: absolute;
width: 100%;
left: 0;
top: 0;
transition: opacity 120ms linear !important; /* override default */
}
.pswp-caption-content {
display: none;
}
.pswp__dynamic-caption a {
color: #fff;
}
.pswp__dynamic-caption--faded {
opacity: 0 !important;
}
.pswp__dynamic-caption--aside {
width: auto;
max-width: 300px;
padding: 20px 15px 20px 20px;
margin-top: 70px;
}
.pswp__dynamic-caption--below {
width: auto;
max-width: 700px;
padding: 15px 0 0;
}
.pswp__dynamic-caption--on-hor-edge {
padding-left: 15px;
padding-right: 15px;
}
.pswp__dynamic-caption--mobile {
width: 100%;
background: rgba(0,0,0,0.5);
padding: 10px 15px;
right: 0;
bottom: 0;
/* override styles that were set via JS.
as they interfere with size measurement */
top: auto !important;
left: 0 !important;
}

9
src/sigal/themes/photoswipe/templates/album.html

@ -1,3 +1,4 @@
{% from 'description.html' import img_description %}
{% extends "base.html" %}
{% block extra_head %}
@ -5,14 +6,16 @@
<script type="module">
import PhotoSwipeLightbox from '/static/photoswipe-lightbox.esm.min.js';
import PhotoSwipe from '/static/photoswipe.esm.min.js';
import PhotoSwipeVideoPlugin from '/static/photoswipe-video-plugin.esm.min.js';
import PhotoSwipeDynamicCaption from '/static/photoswipe-dynamic-caption-plugin.esm.min.js';
import PhotoSwipeFullscreen from '/static/photoswipe-fullscreen.esm.min.js';
import PhotoSwipeVideoPlugin from '/static/photoswipe-video-plugin.esm.min.js';
const lightbox = new PhotoSwipeLightbox({
gallery: '.gallery',
children: 'a',
children: '.thumbnail',
pswpModule: PhotoSwipe
});
const captionPlugin = new PhotoSwipeDynamicCaption(lightbox, {type: 'auto'});
const fullscreenPlugin = new PhotoSwipeFullscreen(lightbox);
const videoPlugin = new PhotoSwipeVideoPlugin(lightbox, {autoplay: true});
lightbox.init();
@ -31,6 +34,7 @@
data-pswp-height="{{media.size.height}}">
<img src="{{ media.thumbnail }}" alt="{{ media.url }}" />
</a>
<div class="pswp-caption-content">{{ img_description(media) }}</div>
<figcaption>{{ media.title }} - {{ media.exif.datetime }}</figcaption>
</figure>
{% endif %}
@ -42,6 +46,7 @@
data-pswp-height="600">
<img src="{{ media.thumbnail }}" alt="{{ media.url }}" />
</a>
<div class="pswp-caption-content">{{ img_description(media) }}</div>
<figcaption>{{ media_title }}</figcaption>
</figure>
{% endif %}

Loading…
Cancel
Save