[FIX] PhotoSwipe crashing on show (#23499)
Co-authored-by: dougfabris <devfabris@gmail.com>pull/23725/head
parent
c320e377a5
commit
7ed68465e5
@ -1,103 +0,0 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Blaze } from 'meteor/blaze'; |
||||
import { Template } from 'meteor/templating'; |
||||
import { escapeHTML } from '@rocket.chat/string-helpers'; |
||||
|
||||
Meteor.startup(() => { |
||||
let currentGallery = null; |
||||
const initGallery = async (items, options) => { |
||||
Blaze.render(Template.photoswipeContent, document.body); |
||||
const [PhotoSwipeImport, PhotoSwipeUI_DefaultImport] = await Promise.all([import('photoswipe'), import('photoswipe/dist/photoswipe-ui-default'), import('photoswipe/dist/photoswipe.css')]); |
||||
if (!currentGallery) { |
||||
const PhotoSwipe = PhotoSwipeImport.default; |
||||
const PhotoSwipeUI_Default = PhotoSwipeUI_DefaultImport.default; |
||||
currentGallery = new PhotoSwipe(document.getElementById('pswp'), PhotoSwipeUI_Default, items, options); |
||||
currentGallery.listen('destroy', () => { |
||||
currentGallery = null; |
||||
}); |
||||
currentGallery.init(); |
||||
} |
||||
}; |
||||
|
||||
const defaultGalleryOptions = { |
||||
bgOpacity: 0.7, |
||||
showHideOpacity: true, |
||||
counterEl: false, |
||||
shareEl: false, |
||||
clickToCloseNonZoomable: false, |
||||
}; |
||||
|
||||
const createEventListenerFor = (className) => (event) => { |
||||
event.preventDefault(); |
||||
event.stopPropagation(); |
||||
|
||||
const galleryOptions = { |
||||
...defaultGalleryOptions, |
||||
index: 0, |
||||
addCaptionHTMLFn(item, captionEl) { |
||||
captionEl.children[0].innerHTML = `${ escapeHTML(item.title) }<br/><small>${ escapeHTML(item.description) }</small>`; |
||||
return true; |
||||
}, |
||||
}; |
||||
|
||||
const items = Array.from(document.querySelectorAll(className)) |
||||
.map((element, i) => { |
||||
if (element === event.currentTarget) { |
||||
galleryOptions.index = i; |
||||
} |
||||
|
||||
const item = { |
||||
src: element.src, |
||||
w: element.naturalWidth, |
||||
h: element.naturalHeight, |
||||
title: element.dataset.title || element.title, |
||||
description: element.dataset.description, |
||||
}; |
||||
|
||||
if (element.dataset.src || element.href) { |
||||
// use stored sizes if available
|
||||
if (element.dataset.width && element.dataset.height) { |
||||
return { |
||||
...item, |
||||
h: element.dataset.height, |
||||
w: element.dataset.width, |
||||
src: element.dataset.src || element.href, |
||||
}; |
||||
} |
||||
|
||||
const img = new Image(); |
||||
|
||||
img.addEventListener('load', () => { |
||||
if (!currentGallery) { |
||||
return; |
||||
} |
||||
|
||||
// stores loaded sizes on original image element
|
||||
element.dataset.width = img.naturalWidth; |
||||
element.dataset.height = img.naturalHeight; |
||||
|
||||
delete currentGallery.items[i].html; |
||||
currentGallery.items[i].src = img.src; |
||||
currentGallery.items[i].w = img.naturalWidth; |
||||
currentGallery.items[i].h = img.naturalHeight; |
||||
currentGallery.invalidateCurrItems(); |
||||
currentGallery.updateSize(true); |
||||
}); |
||||
|
||||
img.src = element.dataset.src || element.href; |
||||
|
||||
return { |
||||
...item, |
||||
msrc: element.src, |
||||
src: element.dataset.src || element.href, |
||||
}; |
||||
} |
||||
|
||||
return item; |
||||
}); |
||||
|
||||
initGallery(items, galleryOptions); |
||||
}; |
||||
|
||||
$(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); |
||||
}); |
@ -1,5 +1,3 @@ |
||||
<template name="photoswipe"></template> |
||||
|
||||
<template name="photoswipeContent"> |
||||
<div class="pswp" id="pswp" tabindex="-1" role="dialog" aria-hidden="true"> |
||||
<div class="pswp__bg"></div> |
@ -0,0 +1,158 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Blaze } from 'meteor/blaze'; |
||||
import { Template } from 'meteor/templating'; |
||||
import { escapeHTML } from '@rocket.chat/string-helpers'; |
||||
import type PhotoSwipe from 'photoswipe'; |
||||
import type PhotoSwipeUiDefault from 'photoswipe/dist/photoswipe-ui-default'; |
||||
|
||||
const parseLength = (x: unknown): number | undefined => { |
||||
const length = typeof x === 'string' ? parseInt(x, 10) : undefined; |
||||
return Number.isFinite(length) ? length : undefined; |
||||
}; |
||||
|
||||
const getImageSize = (src: string): Promise<[w: number, h: number]> => new Promise((resolve, reject) => { |
||||
const img = new Image(); |
||||
|
||||
img.addEventListener('load', () => { |
||||
resolve([img.naturalWidth, img.naturalHeight]); |
||||
}); |
||||
|
||||
img.addEventListener('error', (error) => { |
||||
reject(error.error); |
||||
}); |
||||
|
||||
img.src = src; |
||||
}); |
||||
|
||||
type Slide = PhotoSwipeUiDefault.Item & { description?: string }; |
||||
|
||||
const fromElementToSlide = async (element: Element): Promise<Slide | null> => { |
||||
if (!(element instanceof HTMLElement)) { |
||||
return null; |
||||
} |
||||
|
||||
const title = element.dataset.title || element.title; |
||||
const { description } = element.dataset; |
||||
|
||||
if (element instanceof HTMLAnchorElement) { |
||||
const src = element.dataset.src || element.href; |
||||
let w = parseLength(element.dataset.width); |
||||
let h = parseLength(element.dataset.height); |
||||
|
||||
if (w === undefined || h === undefined) { |
||||
[w, h] = await getImageSize(src); |
||||
} |
||||
|
||||
return { src, w, h, title, description }; |
||||
} |
||||
|
||||
if (element instanceof HTMLImageElement) { |
||||
let msrc: string | undefined; |
||||
let { src } = element; |
||||
let w: number | undefined = element.naturalWidth; |
||||
let h: number | undefined = element.naturalHeight; |
||||
|
||||
if (element.dataset.src) { |
||||
msrc = element.src; |
||||
src = element.dataset.src; |
||||
w = parseLength(element.dataset.width); |
||||
h = parseLength(element.dataset.height); |
||||
|
||||
if (w === undefined || h === undefined) { |
||||
[w, h] = await getImageSize(src); |
||||
} |
||||
} |
||||
|
||||
return { msrc, src, w, h, title, description }; |
||||
} |
||||
|
||||
return null; |
||||
}; |
||||
|
||||
let currentGallery: PhotoSwipe<PhotoSwipeUiDefault.Options> | null = null; |
||||
|
||||
const initGallery = async (items: Slide[], options: PhotoSwipeUiDefault.Options): Promise<void> => { |
||||
const [ |
||||
{ default: PhotoSwipe }, |
||||
{ default: PhotoSwipeUiDefault }, // eslint-disable-line @typescript-eslint/camelcase
|
||||
] = await Promise.all([ |
||||
import('photoswipe'), |
||||
import('photoswipe/dist/photoswipe-ui-default'), |
||||
// @ts-ignore
|
||||
import('photoswipe/dist/photoswipe.css'), |
||||
// @ts-ignore
|
||||
import('./photoswipeContent.html'), |
||||
]); |
||||
|
||||
Blaze.render(Template.photoswipeContent, document.body); |
||||
|
||||
if (!currentGallery) { |
||||
const container = document.getElementById('pswp'); |
||||
|
||||
if (!container) { |
||||
throw new Error('Photoswipe container element not found'); |
||||
} |
||||
|
||||
currentGallery = new PhotoSwipe(container, PhotoSwipeUiDefault, items, options); |
||||
|
||||
currentGallery.listen('destroy', () => { |
||||
currentGallery = null; |
||||
}); |
||||
|
||||
currentGallery.init(); |
||||
} |
||||
}; |
||||
|
||||
const defaultGalleryOptions: PhotoSwipeUiDefault.Options = { |
||||
bgOpacity: 0.7, |
||||
showHideOpacity: true, |
||||
counterEl: false, |
||||
shareEl: false, |
||||
clickToCloseNonZoomable: false, |
||||
index: 0, |
||||
addCaptionHTMLFn(item: Slide, captionEl: HTMLElement): boolean { |
||||
captionEl.children[0].innerHTML = ` |
||||
${ escapeHTML(item.title ?? '') }<br/> |
||||
<small>${ escapeHTML(item.description ?? '') }</small> |
||||
`;
|
||||
return true; |
||||
}, |
||||
}; |
||||
|
||||
const createEventListenerFor = (className: string) => (event: JQuery.ClickEvent): void => { |
||||
event.preventDefault(); |
||||
event.stopPropagation(); |
||||
|
||||
const { currentTarget } = event; |
||||
|
||||
Array.from(document.querySelectorAll(className)) |
||||
.sort((a, b) => { |
||||
if (a === currentTarget) { |
||||
return -1; |
||||
} |
||||
|
||||
if (b === currentTarget) { |
||||
return 1; |
||||
} |
||||
|
||||
return 0; |
||||
}) |
||||
.map((element) => fromElementToSlide(element)) |
||||
.reduce((p, curr) => p.then(() => curr).then(async (slide) => { |
||||
if (!slide) { |
||||
return; |
||||
} |
||||
|
||||
if (!currentGallery) { |
||||
return initGallery([slide], defaultGalleryOptions); |
||||
} |
||||
|
||||
currentGallery.items.push(slide); |
||||
currentGallery.invalidateCurrItems(); |
||||
currentGallery.updateSize(true); |
||||
}), Promise.resolve()); |
||||
}; |
||||
|
||||
Meteor.startup(() => { |
||||
$(document).on('click', '.gallery-item', createEventListenerFor('.gallery-item')); |
||||
}); |
Loading…
Reference in new issue