607 lines
22 KiB
Vue
607 lines
22 KiB
Vue
<script>
|
|
import AppIcon from '@components/AppIcon';
|
|
import ProjectRelTime from '@components/ProjectRelTime';
|
|
import { getStyleProperty } from '@scripts/common';
|
|
|
|
/* lightbox (https://github.com/lokesh/lightbox2) converted to a vue component and improved to support videos */
|
|
|
|
export default {
|
|
components: {
|
|
AppIcon,
|
|
ProjectRelTime
|
|
},
|
|
props: {
|
|
alwaysShowNavOnTouchDevices: {type: Boolean, default: false},
|
|
positionFromTop: {type: Number, default: 50},
|
|
wrapAround: {type: Boolean, default: false},
|
|
disableScrolling: {type: Boolean, default: false},
|
|
hasVideo: {type: Boolean, default: true},
|
|
maxWidth: {type: Number, default: null},
|
|
maxHeight: {type: Number, default: null}
|
|
},
|
|
emits: ['media-change', 'closing'],
|
|
inject: ['isMobile'],
|
|
data() {
|
|
/*
|
|
fadeDuration/imageFadeDuration/resizeDuration read --trans-quick/--trans-slow
|
|
directly rather than being passed down as props from Project.vue: they're
|
|
declared on :root (_common.scss), so they're available immediately.
|
|
No need to wait on Project.vue's own $el to mount first, and no risk of
|
|
a parent-to-child prop update lagging behind the first time this opens.
|
|
*/
|
|
const fadeDuration = parseFloat(getStyleProperty('--trans-quick'));
|
|
const resizeDuration = parseFloat(getStyleProperty('--trans-slow'));
|
|
|
|
return {
|
|
album: [],
|
|
currentImageIndex: 0,
|
|
gMouseDownOffsetX: 0,
|
|
gMouseDownOffsetY: 0,
|
|
resizeTimer: null,
|
|
mediaLoadId: 0,
|
|
containerPadding: null,
|
|
imageBorderWidth: null,
|
|
videoBorderWidth: null,
|
|
fadeDuration,
|
|
imageFadeDuration: fadeDuration,
|
|
resizeDuration
|
|
};
|
|
},
|
|
computed: {
|
|
currentMedia() {
|
|
return this.album[this.currentImageIndex] || null;
|
|
},
|
|
relTimeLines() {
|
|
const media = this.currentMedia;
|
|
if(!media || this.isMobile()) return [];
|
|
|
|
const postedOn = {
|
|
key: 'posted',
|
|
icon: 'upload',
|
|
titleWrapperLangId: 'media.posted_on',
|
|
localTime: media.postedOnLocalTime,
|
|
siteTime: media.postedOnSiteTime,
|
|
offset: media.postedOnDayOffset
|
|
};
|
|
const takenOn = {
|
|
key: 'taken',
|
|
icon: media.type+'-shot',
|
|
titleWrapperLangId: 'media.'+media.type+'_taken_on',
|
|
localTime: media.takenOnLocalTime,
|
|
siteTime: media.takenOnSiteTime,
|
|
offset: media.takenOnDayOffset
|
|
};
|
|
return (media.source == 'marker') ? [takenOn, postedOn] : [postedOn, takenOn];
|
|
}
|
|
},
|
|
mounted() {
|
|
this.setVisible(this.$refs.overlay, false);
|
|
this.setVisible(this.$refs.lightboxEl, false);
|
|
|
|
this.containerPadding = this.getBoxMetrics(this.$refs.container, 'padding');
|
|
this.imageBorderWidth = this.getBoxMetrics(this.$refs.image, 'border');
|
|
this.videoBorderWidth = this.getBoxMetrics(this.$refs.video, 'border');
|
|
|
|
this.$refs.nav.addEventListener('wheel', this.onWheel, {passive: false});
|
|
this.$refs.nav.addEventListener('mousedown', this.onDragStart);
|
|
window.addEventListener('mouseup', this.onDragEnd);
|
|
},
|
|
beforeUnmount() {
|
|
if(this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
window.removeEventListener('mouseup', this.onDragEnd);
|
|
window.removeEventListener('resize', this.sizeOverlay);
|
|
window.removeEventListener('mousemove', this.onDragMove);
|
|
},
|
|
methods: {
|
|
open(album, startId, source) {
|
|
this.album = album.map((options) => this.mapMedia(options, source));
|
|
const startIndex = Math.max(this.album.findIndex((media) => media.id == startId), 0);
|
|
|
|
this.fade(this.$refs.overlay, true, this.fadeDuration);
|
|
this.fade(this.$refs.lightboxEl, true, this.fadeDuration);
|
|
|
|
if(this.disableScrolling) document.body.classList.add('lb-disable-scrolling');
|
|
|
|
window.addEventListener('resize', this.sizeOverlay);
|
|
this.changeImage(startIndex);
|
|
},
|
|
end(dispose = false) {
|
|
this.mediaLoadId++; //Drop any pending image/video load
|
|
this.disableKeyboardNav();
|
|
this.$refs.video?.pause();
|
|
this.$refs.video?.removeAttribute('src');
|
|
this.$refs.container?.classList.remove('lb-video-nav', 'moveable', 'moving');
|
|
window.removeEventListener('resize', this.sizeOverlay);
|
|
window.removeEventListener('mousemove', this.onDragMove);
|
|
|
|
if(dispose) {
|
|
this.album = [];
|
|
}
|
|
else {
|
|
this.fade(this.$refs.lightboxEl, false, this.fadeDuration);
|
|
this.fade(this.$refs.overlay, false, this.fadeDuration);
|
|
this.$emit('closing');
|
|
}
|
|
|
|
if(this.disableScrolling) document.body.classList.remove('lb-disable-scrolling');
|
|
},
|
|
mapMedia(options, source) {
|
|
return {
|
|
alt: '',
|
|
link: options.media_path,
|
|
orientation: parseInt(options.rotate || '0', 10),
|
|
type: options.subtype || 'image',
|
|
id: options.id_media,
|
|
width: parseInt(options.width || '0', 10),
|
|
height: parseInt(options.height || '0', 10),
|
|
source,
|
|
comment: options.comment || '',
|
|
postedOnLocalTime: options.posted_on_formatted_time_local || '',
|
|
postedOnSiteTime: options.posted_on_formatted_time || '',
|
|
postedOnDayOffset: options.posted_on_day_offset || '',
|
|
takenOnLocalTime: options.taken_on_formatted_time_local || '',
|
|
takenOnSiteTime: options.taken_on_formatted_time || '',
|
|
takenOnDayOffset: options.taken_on_day_offset || ''
|
|
};
|
|
},
|
|
hasMediaAfterCurrent() {
|
|
return this.currentImageIndex < this.album.length - 1;
|
|
},
|
|
refreshAlbum(album) {
|
|
const current = this.currentMedia;
|
|
if(!current) return;
|
|
|
|
const existingIds = new Set(this.album.map((media) => media.id));
|
|
album.forEach((options) => {
|
|
if(existingIds.has(options.id_media)) return;
|
|
|
|
this.album.push(this.mapMedia(options, current.source));
|
|
existingIds.add(options.id_media);
|
|
});
|
|
|
|
this.updateNav();
|
|
},
|
|
getMaxSizes(mediaType) {
|
|
let maxWidth = window.innerWidth - this.containerPadding.left - this.containerPadding.right;
|
|
let maxHeight = window.innerHeight - this.containerPadding.top - this.containerPadding.bottom - this.positionFromTop;
|
|
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
|
maxWidth -= border.left + border.right;
|
|
maxHeight -= border.top + border.bottom;
|
|
maxHeight -= this.getDataContainerHeight(maxWidth + this.containerPadding.left + this.containerPadding.right + border.left + border.right);
|
|
|
|
return {
|
|
maxWidth: Math.max(maxWidth, 1),
|
|
maxHeight: Math.max(maxHeight, 1)
|
|
};
|
|
},
|
|
getDataContainerHeight(width = null) {
|
|
if(!this.$refs.dataContainer) return 0;
|
|
|
|
const currentWidth = this.$refs.dataContainer.style.width;
|
|
if(width !== null) this.$refs.dataContainer.style.width = `${width}px`;
|
|
const height = Math.ceil(this.$refs.dataContainer.getBoundingClientRect().height || this.$refs.dataContainer.offsetHeight || 0);
|
|
this.$refs.dataContainer.style.width = currentWidth;
|
|
|
|
return height;
|
|
},
|
|
getMediaSize(media, maxWidth, maxHeight) {
|
|
if(media.width <= maxWidth && media.height <= maxHeight) {
|
|
return {
|
|
width: media.width,
|
|
height: media.height
|
|
};
|
|
}
|
|
|
|
const widthRatio = media.width / maxWidth;
|
|
const heightRatio = media.height / maxHeight;
|
|
|
|
if(widthRatio > heightRatio) {
|
|
return {
|
|
width: maxWidth,
|
|
height: Math.round(media.height / widthRatio)
|
|
};
|
|
}
|
|
|
|
return {
|
|
width: Math.round(media.width / heightRatio),
|
|
height: maxHeight
|
|
};
|
|
},
|
|
fitSizeWithDataContainer(size, mediaType) {
|
|
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
|
const maxOuterHeight = Math.max(window.innerHeight - this.positionFromTop, 1);
|
|
let fittedSize = size;
|
|
|
|
for(let i = 0; i < 5; i++) {
|
|
const containerWidth = fittedSize.width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
|
|
const containerHeight = fittedSize.height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
|
|
const dataHeight = this.getDataContainerHeight(containerWidth);
|
|
const overflow = Math.ceil(containerHeight + dataHeight - maxOuterHeight);
|
|
if(overflow <= 0 || fittedSize.height <= 1) break;
|
|
|
|
const height = Math.max(fittedSize.height - overflow, 1);
|
|
fittedSize = {
|
|
width: Math.max(Math.round(fittedSize.width * (height / fittedSize.height)), 1),
|
|
height
|
|
};
|
|
}
|
|
|
|
return fittedSize;
|
|
},
|
|
updateSize(index) {
|
|
const media = this.album[index];
|
|
const maxSizes = this.getMaxSizes(media.type);
|
|
const maxWidth = this.maxWidth ? Math.min(this.maxWidth, maxSizes.maxWidth) : maxSizes.maxWidth;
|
|
const maxHeight = this.maxHeight ? Math.min(this.maxHeight, maxSizes.maxHeight) : maxSizes.maxHeight;
|
|
const size = this.fitSizeWithDataContainer(this.getMediaSize(media, maxWidth, maxHeight), media.type);
|
|
|
|
const target = media.type === 'video' ? this.$refs.video : this.$refs.image;
|
|
target.width = size.width;
|
|
target.height = size.height;
|
|
this.sizeContainer(size.width, size.height, media.type);
|
|
},
|
|
changeImage(index) {
|
|
const media = this.album[index];
|
|
if(!media) return;
|
|
const loadId = ++this.mediaLoadId;
|
|
|
|
this.updateDetails(media, false);
|
|
this.hideElements([this.$refs.dataContainer]);
|
|
this.disableKeyboardNav();
|
|
this.fade(this.$refs.overlay, true, this.fadeDuration);
|
|
this.fade(this.$refs.loader, true, 200);
|
|
this.hideElements([this.$refs.image, this.$refs.video, this.$refs.nav, this.$refs.prev, this.$refs.next]);
|
|
this.resetImageTransform();
|
|
this.$refs.outerContainer.classList.add('animating');
|
|
this.$refs.container.classList.remove('moveable', 'moving', 'lb-video-nav');
|
|
this.currentImageIndex = index;
|
|
|
|
this.$emit('media-change', media);
|
|
|
|
if(media.type === 'video') {
|
|
this.$refs.image.removeAttribute('src');
|
|
this.$refs.container.classList.add('lb-video-nav');
|
|
this.$refs.video.onloadedmetadata = () => {
|
|
if(loadId !== this.mediaLoadId) return;
|
|
media.width = this.$refs.video.videoWidth;
|
|
media.height = this.$refs.video.videoHeight;
|
|
this.$refs.video.onloadedmetadata = null;
|
|
this.updateSize(index);
|
|
};
|
|
this.$refs.video.src = media.link;
|
|
} else {
|
|
this.$refs.video.pause();
|
|
this.$refs.video.removeAttribute('src');
|
|
//Load & decode in a detached image, so that neither the placeholder nor the previous picture can be measured or painted
|
|
const loader = new Image();
|
|
loader.src = media.link;
|
|
loader.decode().then(() => {
|
|
//A previous request may still resolve after a new one started: only the latest one counts
|
|
if(loadId !== this.mediaLoadId) return;
|
|
|
|
let width = loader.naturalWidth;
|
|
let height = loader.naturalHeight;
|
|
if(Math.abs(media.orientation) === 90 && width > height) {
|
|
const tmp = width;
|
|
width = height;
|
|
height = tmp;
|
|
}
|
|
media.width = width;
|
|
media.height = height;
|
|
this.$refs.image.alt = media.alt;
|
|
this.$refs.image.src = media.link;
|
|
this.updateSize(index);
|
|
}).catch((error) => {
|
|
if(loadId === this.mediaLoadId) console.error('Lightbox: unable to load image', media.link, error);
|
|
});
|
|
}
|
|
},
|
|
sizeOverlay() {
|
|
if(this.resizeTimer) clearTimeout(this.resizeTimer);
|
|
if(!this.album.length) return;
|
|
|
|
this.resizeTimer = window.setTimeout(() => {
|
|
const current = this.album[this.currentImageIndex];
|
|
if(!current) return;
|
|
if(current.type === 'image') this.changeImage(this.currentImageIndex);
|
|
else this.updateSize(this.currentImageIndex);
|
|
}, 200);
|
|
},
|
|
sizeContainer(width, height, mediaType = 'image') {
|
|
const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
|
|
const newWidth = width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
|
|
const newHeight = height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
|
|
const dataHeight = this.getDataContainerHeight(newWidth);
|
|
|
|
this.$refs.outerContainer.style.transition = `width ${this.resizeDuration}ms, height ${this.resizeDuration}ms`;
|
|
this.$refs.outerContainer.style.width = `${newWidth}px`;
|
|
this.$refs.outerContainer.style.height = `${newHeight + dataHeight}px`;
|
|
this.$refs.container.style.height = `${newHeight}px`;
|
|
|
|
window.setTimeout(() => {
|
|
this.$refs.overlay.focus();
|
|
this.showImage();
|
|
this.$refs.outerContainer.style.transition = '';
|
|
}, this.resizeDuration);
|
|
},
|
|
showImage() {
|
|
this.fade(this.$refs.loader, false, 0);
|
|
if(this.hasVideo && this.album[this.currentImageIndex].type === 'video') this.fade(this.$refs.video, true, this.imageFadeDuration);
|
|
else this.fade(this.$refs.image, true, this.imageFadeDuration);
|
|
|
|
this.updateNav();
|
|
this.updateDetails();
|
|
this.preloadNeighboringImages();
|
|
this.enableKeyboardNav();
|
|
},
|
|
updateNav() {
|
|
this.setVisible(this.$refs.nav, true);
|
|
this.setVisible(this.$refs.prev, false);
|
|
this.setVisible(this.$refs.next, false);
|
|
|
|
const alwaysShowNav = ('ontouchstart' in window) && this.alwaysShowNavOnTouchDevices;
|
|
if(this.album.length <= 1) return;
|
|
|
|
if(this.wrapAround) {
|
|
this.setVisible(this.$refs.prev, true);
|
|
this.setVisible(this.$refs.next, true);
|
|
} else {
|
|
if(this.currentImageIndex > 0) this.setVisible(this.$refs.prev, true);
|
|
if(this.currentImageIndex < this.album.length - 1) this.setVisible(this.$refs.next, true);
|
|
}
|
|
|
|
if(alwaysShowNav) {
|
|
this.$refs.prev.style.opacity = '1';
|
|
this.$refs.next.style.opacity = '1';
|
|
} else {
|
|
this.$refs.prev.style.opacity = '';
|
|
this.$refs.next.style.opacity = '';
|
|
}
|
|
},
|
|
hasCaption(media) {
|
|
return !!(media && (media.comment || media.postedOnSiteTime || media.takenOnSiteTime));
|
|
},
|
|
updateDetails(media = this.album[this.currentImageIndex], show = true) {
|
|
if(!media) return;
|
|
|
|
if(this.hasCaption(media)) {
|
|
if(show) this.fade(this.$refs.caption, true, 200);
|
|
else this.setVisible(this.$refs.caption, true);
|
|
} else {
|
|
this.setVisible(this.$refs.caption, false);
|
|
}
|
|
|
|
if(show) {
|
|
this.fade(this.$refs.closeButton, true, 200);
|
|
this.$refs.outerContainer.classList.remove('animating');
|
|
this.fade(this.$refs.dataContainer, true, this.resizeDuration);
|
|
} else {
|
|
this.setVisible(this.$refs.closeButton, true);
|
|
this.setVisible(this.$refs.dataContainer, false);
|
|
this.$refs.dataContainer.style.transition = '';
|
|
this.$refs.dataContainer.style.opacity = '0';
|
|
}
|
|
},
|
|
preloadNeighboringImages() {
|
|
const next = this.album[this.currentImageIndex + 1];
|
|
const prev = this.album[this.currentImageIndex - 1];
|
|
if(next && next.type === 'image') {
|
|
const preloadNext = new Image();
|
|
preloadNext.src = next.link;
|
|
}
|
|
if(prev && prev.type === 'image') {
|
|
const preloadPrev = new Image();
|
|
preloadPrev.src = prev.link;
|
|
}
|
|
},
|
|
enableKeyboardNav() {
|
|
this.disableKeyboardNav();
|
|
this.$refs.lightboxEl.addEventListener('keyup', this.keyboardAction);
|
|
this.$refs.overlay.addEventListener('keyup', this.keyboardAction);
|
|
},
|
|
disableKeyboardNav() {
|
|
this.$refs.lightboxEl?.removeEventListener('keyup', this.keyboardAction);
|
|
this.$refs.overlay?.removeEventListener('keyup', this.keyboardAction);
|
|
},
|
|
keyboardAction(event) {
|
|
switch(event.key) {
|
|
case 'Escape':
|
|
event.stopPropagation();
|
|
this.end();
|
|
break;
|
|
case 'ArrowLeft':
|
|
if(this.currentImageIndex !== 0) this.changeImage(this.currentImageIndex - 1);
|
|
else if(this.wrapAround && this.album.length > 1) this.changeImage(this.album.length - 1);
|
|
break;
|
|
case 'ArrowRight':
|
|
if(this.currentImageIndex !== this.album.length - 1) this.changeImage(this.currentImageIndex + 1);
|
|
else if(this.wrapAround && this.album.length > 1) this.changeImage(0);
|
|
break;
|
|
}
|
|
},
|
|
onCloseKeyup(event) {
|
|
if(event.key === 'Enter' || event.key === ' ') this.end();
|
|
},
|
|
onPrevClick() {
|
|
if(this.currentImageIndex === 0) this.changeImage(this.album.length - 1);
|
|
else this.changeImage(this.currentImageIndex - 1);
|
|
},
|
|
onNextClick() {
|
|
if(this.currentImageIndex === this.album.length - 1) this.changeImage(0);
|
|
else this.changeImage(this.currentImageIndex + 1);
|
|
},
|
|
onOuterContainerClick(event) {
|
|
if(event.target === this.$refs.outerContainer) this.end();
|
|
},
|
|
onLightboxClick(event) {
|
|
if(event.target === this.$refs.lightboxEl) this.end();
|
|
},
|
|
onWheel(event) {
|
|
const media = this.album[this.currentImageIndex];
|
|
if(!media || media.type === 'video') return;
|
|
event.preventDefault();
|
|
|
|
const rect = this.$refs.image.getBoundingClientRect();
|
|
const oldTransform = this.getImageTransform();
|
|
const oldZoom = oldTransform.scale;
|
|
const maxZoom = Math.max(media.width / Math.max(this.$refs.image.width, 1), media.height / Math.max(this.$refs.image.height, 1), 1);
|
|
const newZoom = Math.min(Math.max(oldZoom + (-Math.sign(event.deltaY) / 10), 1), maxZoom);
|
|
|
|
const imageCenterX = rect.left + rect.width / 2 - oldTransform.translateX;
|
|
const imageCenterY = rect.top + rect.height / 2 - oldTransform.translateY;
|
|
const cursorX = event.clientX - imageCenterX;
|
|
const cursorY = event.clientY - imageCenterY;
|
|
const zoomRatio = newZoom / oldZoom;
|
|
const transform = this.clampImageTransform({
|
|
scale: newZoom,
|
|
translateX: cursorX - zoomRatio * (cursorX - oldTransform.translateX),
|
|
translateY: cursorY - zoomRatio * (cursorY - oldTransform.translateY)
|
|
});
|
|
|
|
this.$refs.container.classList.toggle('moveable', newZoom > 1);
|
|
this.setImageTransform(transform);
|
|
},
|
|
onDragStart(event) {
|
|
const scale = parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1');
|
|
if(scale <= 1) return;
|
|
|
|
this.gMouseDownOffsetX = event.clientX - parseFloat(this.$refs.image.style.getPropertyValue('--translate-x') || '0');
|
|
this.gMouseDownOffsetY = event.clientY - parseFloat(this.$refs.image.style.getPropertyValue('--translate-y') || '0');
|
|
this.$refs.container.classList.add('moving');
|
|
window.addEventListener('mousemove', this.onDragMove);
|
|
},
|
|
onDragMove(event) {
|
|
const zoom = parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1');
|
|
const transform = this.clampImageTransform({
|
|
scale: zoom,
|
|
translateX: event.clientX - this.gMouseDownOffsetX,
|
|
translateY: event.clientY - this.gMouseDownOffsetY
|
|
});
|
|
|
|
this.setImageTransform(transform);
|
|
},
|
|
onDragEnd() {
|
|
window.removeEventListener('mousemove', this.onDragMove);
|
|
this.$refs.container?.classList.remove('moving');
|
|
},
|
|
getBoxMetrics(element, type) {
|
|
const styles = getComputedStyle(element);
|
|
return {
|
|
top: parseInt(styles[`${type}-top-width`], 10) || 0,
|
|
right: parseInt(styles[`${type}-right-width`], 10) || 0,
|
|
bottom: parseInt(styles[`${type}-bottom-width`], 10) || 0,
|
|
left: parseInt(styles[`${type}-left-width`], 10) || 0
|
|
};
|
|
},
|
|
resetImageTransform() {
|
|
this.setImageTransform({scale: 1, translateX: 0, translateY: 0});
|
|
},
|
|
getImageTransform() {
|
|
return {
|
|
scale: parseFloat(this.$refs.image.style.getPropertyValue('--scale') || '1'),
|
|
translateX: parseFloat(this.$refs.image.style.getPropertyValue('--translate-x') || '0'),
|
|
translateY: parseFloat(this.$refs.image.style.getPropertyValue('--translate-y') || '0')
|
|
};
|
|
},
|
|
clampImageTransform(transform) {
|
|
const maxTranslateX = (transform.scale - 1) * this.$refs.image.width / 2;
|
|
const maxTranslateY = (transform.scale - 1) * this.$refs.image.height / 2;
|
|
|
|
return {
|
|
scale: transform.scale,
|
|
translateX: Math.max(Math.min(transform.translateX, maxTranslateX), -maxTranslateX),
|
|
translateY: Math.max(Math.min(transform.translateY, maxTranslateY), -maxTranslateY)
|
|
};
|
|
},
|
|
setImageTransform(transform) {
|
|
if(!this.$refs.image) return;
|
|
this.$refs.image.style.setProperty('--scale', String(transform.scale));
|
|
this.$refs.image.style.setProperty('--translate-x', `${transform.translateX}px`);
|
|
this.$refs.image.style.setProperty('--translate-y', `${transform.translateY}px`);
|
|
},
|
|
hideElements(elements) {
|
|
elements.forEach((element) => {
|
|
this.setVisible(element, false);
|
|
});
|
|
},
|
|
setVisible(element, visible) {
|
|
if(!element) return;
|
|
element.style.visibility = visible ? 'visible' : 'hidden';
|
|
element.style.pointerEvents = visible ? '' : 'none';
|
|
},
|
|
fade(element, show, duration, done) {
|
|
if(!element) return;
|
|
|
|
const safeDuration = duration || 0;
|
|
element.style.transition = `opacity ${safeDuration}ms`;
|
|
if(show) {
|
|
this.setVisible(element, true);
|
|
requestAnimationFrame(() => {
|
|
element.style.opacity = element === this.$refs.overlay ? '0.8' : '1';
|
|
});
|
|
} else {
|
|
element.style.opacity = '0';
|
|
element.style.pointerEvents = 'none';
|
|
window.setTimeout(() => {
|
|
this.setVisible(element, false);
|
|
}, safeDuration);
|
|
}
|
|
|
|
if(typeof done === 'function') {
|
|
window.setTimeout(done, safeDuration);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<Teleport to="body">
|
|
<div id="lightboxOverlay" ref="overlay" tabindex="-1" class="lightboxOverlay" @click="end()"></div>
|
|
<div id="lightbox" ref="lightboxEl" tabindex="-1" class="lightbox" @click="onLightboxClick">
|
|
<div class="lb-outerContainer" ref="outerContainer" @click.stop="onOuterContainerClick">
|
|
<div class="lb-container" ref="container">
|
|
<img class="lb-image" ref="image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" alt="" />
|
|
<video class="lb-video" ref="video" controls autoplay></video>
|
|
<div class="lb-nav" ref="nav">
|
|
<div class="lb-prev-area">
|
|
<a class="lb-prev" ref="prev" aria-label="Previous image" href="" role="button" @click.prevent="onPrevClick">
|
|
<AppIcon :icon="'prev'" />
|
|
</a>
|
|
</div>
|
|
<div class="lb-next-area">
|
|
<a class="lb-next" ref="next" aria-label="Next image" href="" role="button" @click.prevent="onNextClick"><AppIcon :icon="'next'" /></a>
|
|
</div>
|
|
</div>
|
|
<div class="lb-loader" ref="loader" @click.prevent="end()">
|
|
<a class="lb-cancel" ref="cancel" href="#">
|
|
<AppIcon :icon="'cancel'" />
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="lb-dataContainer" ref="dataContainer" @click="end()">
|
|
<div class="lb-data">
|
|
<div class="lb-details">
|
|
<span class="lb-caption" ref="caption">
|
|
<template v-if="hasCaption(currentMedia)">
|
|
<span v-if="!!currentMedia?.comment" class="lb-caption-line comment">
|
|
<AppIcon icon="post" width="fixed" size="lg" text-classes="comment-text" :text="currentMedia.comment" />
|
|
</span>
|
|
<span v-for="line in relTimeLines" :key="line.key" class="lb-caption-line">
|
|
<ProjectRelTime :icon="line.icon" :titleWrapperLangId="line.titleWrapperLangId" :localTime="line.localTime" :siteTime="line.siteTime" :offset="line.offset" />
|
|
</span>
|
|
</template>
|
|
</span>
|
|
</div>
|
|
<div class="lb-closeContainer">
|
|
<a class="lb-close" ref="closeButton" href="#" role="button" @click.prevent.stop="end()" @keyup="onCloseKeyup">
|
|
<AppIcon :icon="'close'" :classes="'fa-lg'" />
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Teleport>
|
|
</template>
|