|
/**
|
|
* @fileoverview Image Preview Component for Rantii
|
|
* @author retoor <retoor@molodetz.nl>
|
|
* @description Displays images with lightbox functionality
|
|
* @keywords image, preview, lightbox, gallery, media
|
|
*/
|
|
|
|
import { BaseComponent } from './base-component.js';
|
|
import { buildDevrantImageUrl, isGifUrl } from '../utils/url.js';
|
|
|
|
class ImagePreview extends BaseComponent {
|
|
static get observedAttributes() {
|
|
return ['src', 'width', 'height', 'alt'];
|
|
}
|
|
|
|
init() {
|
|
this.render();
|
|
this.bindEvents();
|
|
}
|
|
|
|
render() {
|
|
const src = this.getAttr('src');
|
|
const width = this.getAttr('width');
|
|
const height = this.getAttr('height');
|
|
const alt = this.getAttr('alt') || 'Image';
|
|
|
|
if (!src) {
|
|
this.setHtml('');
|
|
return;
|
|
}
|
|
|
|
const imageUrl = buildDevrantImageUrl(src);
|
|
const isGif = isGifUrl(imageUrl);
|
|
const aspectRatio = width && height ? width / height : null;
|
|
|
|
this.addClass('image-preview');
|
|
|
|
this.setHtml(`
|
|
<div class="image-container" ${aspectRatio ? `style="aspect-ratio: ${aspectRatio}"` : ''}>
|
|
<img class="preview-image"
|
|
src="${imageUrl}"
|
|
alt="${alt}"
|
|
loading="lazy"
|
|
${width ? `width="${width}"` : ''}
|
|
${height ? `height="${height}"` : ''}>
|
|
${isGif ? '<span class="gif-badge">GIF</span>' : ''}
|
|
<button class="expand-btn" aria-label="View full size">
|
|
<svg viewBox="0 0 24 24" width="20" height="20">
|
|
<path fill="currentColor" d="M21 11V3h-8l3.29 3.29-10 10L3 13v8h8l-3.29-3.29 10-10z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
`);
|
|
}
|
|
|
|
bindEvents() {
|
|
this.on(this, 'click', this.handleClick);
|
|
}
|
|
|
|
handleClick(e) {
|
|
const expandBtn = e.target.closest('.expand-btn');
|
|
const img = e.target.closest('.preview-image');
|
|
|
|
if (expandBtn || img) {
|
|
e.preventDefault();
|
|
this.openLightbox();
|
|
}
|
|
}
|
|
|
|
openLightbox() {
|
|
const src = this.getAttr('src');
|
|
if (!src) return;
|
|
|
|
const imageUrl = buildDevrantImageUrl(src);
|
|
|
|
const lightbox = document.createElement('image-lightbox');
|
|
lightbox.setAttribute('src', imageUrl);
|
|
document.body.appendChild(lightbox);
|
|
}
|
|
|
|
onAttributeChanged(name, oldValue, newValue) {
|
|
this.render();
|
|
}
|
|
}
|
|
|
|
customElements.define('image-preview', ImagePreview);
|
|
|
|
class ImageLightbox extends BaseComponent {
|
|
static get observedAttributes() {
|
|
return ['src'];
|
|
}
|
|
|
|
init() {
|
|
this.keydownHandler = (e) => this.handleKeydown(e);
|
|
this.render();
|
|
this.bindEvents();
|
|
}
|
|
|
|
render() {
|
|
const src = this.getAttr('src');
|
|
|
|
this.addClass('lightbox');
|
|
|
|
this.setHtml(`
|
|
<div class="lightbox-backdrop"></div>
|
|
<div class="lightbox-content">
|
|
<button class="lightbox-close" aria-label="Close">
|
|
<svg viewBox="0 0 24 24" width="24" height="24">
|
|
<path fill="currentColor" d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
|
</svg>
|
|
</button>
|
|
<img class="lightbox-image" src="${src}" alt="Full size image">
|
|
</div>
|
|
`);
|
|
|
|
requestAnimationFrame(() => this.addClass('lightbox-visible'));
|
|
}
|
|
|
|
bindEvents() {
|
|
this.on(this, 'click', this.handleClick);
|
|
document.addEventListener('keydown', this.keydownHandler);
|
|
}
|
|
|
|
handleClick(e) {
|
|
if (e.target.classList.contains('lightbox-backdrop') ||
|
|
e.target.closest('.lightbox-close')) {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
handleKeydown(e) {
|
|
if (e.key === 'Escape') {
|
|
this.close();
|
|
}
|
|
}
|
|
|
|
close() {
|
|
document.removeEventListener('keydown', this.keydownHandler);
|
|
this.removeClass('lightbox-visible');
|
|
setTimeout(() => this.remove(), 300);
|
|
}
|
|
}
|
|
|
|
customElements.define('image-lightbox', ImageLightbox);
|
|
|
|
export { ImagePreview, ImageLightbox };
|