Browse Source

Image gallery

imwald
Nuša Pukšič 3 months ago
parent
commit
cc7dde8520
  1. 63
      assets/controllers/gallery_controller.js
  2. 39
      assets/styles/03-components/picture-event.css
  3. 73
      templates/event/_kind20_picture.html.twig

63
assets/controllers/gallery_controller.js

@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="gallery"
export default class extends Controller {
static targets = ["mainImage", "thumbnail"];
connect() {
this._onKeyDown = this._onKeyDown.bind(this);
this.element.addEventListener('keydown', this._onKeyDown);
// Make the gallery focusable for keyboard events
this.element.setAttribute('tabindex', '0');
}
disconnect() {
this.element.removeEventListener('keydown', this._onKeyDown);
}
switch(event) {
const index = event.currentTarget.dataset.galleryIndex;
this.showImageAtIndex(Number(index));
}
showImageAtIndex(index) {
const thumbnails = this.thumbnailTargets;
if (!thumbnails[index]) return;
const thumbnail = thumbnails[index];
const main = this.mainImageTarget;
main.src = thumbnail.src;
main.alt = thumbnail.alt;
if (thumbnail.dataset.dimensions) {
main.setAttribute('data-dimensions', thumbnail.dataset.dimensions);
} else {
main.removeAttribute('data-dimensions');
}
if (thumbnail.dataset.blurhash) {
main.setAttribute('data-blurhash', thumbnail.dataset.blurhash);
} else {
main.removeAttribute('data-blurhash');
}
thumbnails.forEach(t => t.classList.remove('selected'));
thumbnail.classList.add('selected');
// Store current index for keyboard navigation
this.currentIndex = index;
}
_onKeyDown(event) {
if (!this.thumbnailTargets.length) return;
// Find the currently selected index
let current = this.currentIndex;
if (typeof current !== 'number') {
current = this.thumbnailTargets.findIndex(t => t.classList.contains('selected'));
}
if (event.key === 'ArrowRight') {
event.preventDefault();
const next = (current + 1) % this.thumbnailTargets.length;
this.showImageAtIndex(next);
} else if (event.key === 'ArrowLeft') {
event.preventDefault();
const prev = (current - 1 + this.thumbnailTargets.length) % this.thumbnailTargets.length;
this.showImageAtIndex(prev);
}
}
}

39
assets/styles/03-components/picture-event.css

@ -30,3 +30,42 @@ figcaption.picture-description .credits { @@ -30,3 +30,42 @@ figcaption.picture-description .credits {
font-size: 1rem;
line-height: 1.2;
}
.gallery-view .thumbnails {
display: flex;
gap: var(--spacing-1);
margin-top: var(--spacing-1);
justify-content: flex-start;
flex-wrap: wrap;
}
.gallery-view .thumbnail {
width: 64px;
height: 64px;
object-fit: cover;
border: 2px solid transparent;
cursor: pointer;
transition: border 0.2s;
}
.gallery-view .thumbnail.selected {
border: 2px solid var(--color-secondary);
}
.gallery-view .thumbnail:focus {
outline: none;
border: 2px solid transparent;
box-shadow: none;
}
.gallery-view .thumbnail:focus-visible {
outline: 1px solid var(--color-secondary);
border-radius: 0;
border: 2px solid transparent;
box-shadow: none;
}
.main-image-wrapper:focus {
outline: none;
}
.gallery-view:focus-visible {
outline: 1px solid var(--color-secondary);
border-radius: 0;
}

73
templates/event/_kind20_picture.html.twig

@ -35,7 +35,8 @@ @@ -35,7 +35,8 @@
<div class="picture-gallery">
{% endif %}
{# Display images from imeta tags #}
{# Collect all imeta images into an array #}
{% set images = [] %}
{% for tag in event.tags %}
{% if tag[0] == 'imeta' %}
{% set imageUrl = null %}
@ -45,7 +46,6 @@ @@ -45,7 +46,6 @@
{% set altText = null %}
{% set fallbacks = [] %}
{% set annotatedUsers = [] %}
{# Parse imeta tag parameters #}
{% for i in 1..(tag|length - 1) %}
{% set param = tag[i] %}
@ -65,33 +65,61 @@ @@ -65,33 +65,61 @@
{% set annotatedUsers = annotatedUsers|merge([param[14:]]) %}
{% endif %}
{% endfor %}
{# Alt is also own tag #}
{% for tag in event.tags %}
{% if tag[0] == 'alt' %}
{% set altText = tag[1] %}
{% for altTag in event.tags %}
{% if altTag[0] == 'alt' %}
{% set altText = altTag[1] %}
{% endif %}
{% endfor %}
{% set images = images|merge([{
'url': imageUrl,
'mimeType': mimeType,
'blurhash': blurhash,
'dimensions': dimensions,
'altText': altText,
'fallbacks': fallbacks,
'annotatedUsers': annotatedUsers
}]) %}
{% endif %}
{% endfor %}
{# Display the image with fallbacks and annotations #}
{% if imageUrl %}
<div class="picture-item">
{% if images|length > 0 %}
<div class="gallery-view" data-controller="gallery">
<div class="main-image-wrapper">
{% set main = images[0] %}
<figure class="media">
{% if isEmbed %}
<a href="{{ path('nevent', {nevent: event.id|nEncode }) }}" aria-label="View standalone">
{% endif %}
<picture>
{% for fallback in fallbacks %}
{% for fallback in main.fallbacks %}
<source srcset="{{ fallback }}" />
{% endfor %}
<img src="{{ imageUrl }}"
alt="{{ altText|default('Picture') }}"
{% if dimensions %}data-dimensions="{{ dimensions }}"{% endif %}
{% if blurhash %}data-blurhash="{{ blurhash }}"{% endif %}
class="picture-image"
onerror="if(this.nextSibling) this.src=this.nextSibling.srcset; else this.parentElement.innerHTML='<p class=error>Image failed to load</p>';" />
<img src="{{ main.url }}"
alt="{{ main.altText|default('Picture') }}"
{% if main.dimensions %}data-dimensions="{{ main.dimensions }}"{% endif %}
{% if main.blurhash %}data-blurhash="{{ main.blurhash }}"{% endif %}
class="picture-image main-image"
data-gallery-target="mainImage"
/>
</picture>
{% if images|length > 1 %}
<div class="thumbnails" data-gallery-target="thumbnails">
{% for img in images %}
<img src="{{ img.url }}"
alt="{{ img.altText|default('Picture') }}"
class="thumbnail{% if loop.first %} selected{% endif %}"
data-gallery-target="thumbnail"
data-action="click->gallery#switch"
data-gallery-index="{{ loop.index0 }}"
{% if img.dimensions %}data-dimensions="{{ img.dimensions }}"{% endif %}
{% if img.blurhash %}data-blurhash="{{ img.blurhash }}"{% endif %}
/>
{% endfor %}
</div>
{% endif %}
{% if isEmbed %}
</a>
{% endif %}
@ -102,11 +130,10 @@ @@ -102,11 +130,10 @@
{% endif %}
</figcaption>
</figure>
{# Display annotated users #}
{% if annotatedUsers|length > 0 %}
{# Display annotated users for main image #}
{% if main.annotatedUsers|length > 0 %}
<div class="annotated-users">
{% for userAnnotation in annotatedUsers %}
{% for userAnnotation in main.annotatedUsers %}
{% set parts = userAnnotation|split(':') %}
{% if parts|length == 3 %}
<div class="user-tag" data-left="{{ parts[1] }}" data-top="{{ parts[2] }}" style="left: {{ parts[1] }}%; top: {{ parts[2] }}%;">
@ -117,10 +144,8 @@ @@ -117,10 +144,8 @@
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
{% endfor %}
</div>
{% endif %}
{# Location data #}
{% set location = null %}

Loading…
Cancel
Save