From 7ed68465e5a948a42b8e4fa6c2b63c084c78d326 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 16 Nov 2021 12:54:53 -0300 Subject: [PATCH] [FIX] PhotoSwipe crashing on show (#23499) Co-authored-by: dougfabris --- app/ui-master/client/main.html | 1 - app/ui/client/{index.js => index.ts} | 3 +- app/ui/client/views/app/photoswipe.js | 103 ------------ ...photoswipe.html => photoswipeContent.html} | 4 +- app/ui/client/views/app/photoswipeContent.ts | 158 ++++++++++++++++++ app/ui/{index.js => index.ts} | 0 package-lock.json | 6 + package.json | 1 + 8 files changed, 167 insertions(+), 109 deletions(-) rename app/ui/client/{index.js => index.ts} (94%) delete mode 100644 app/ui/client/views/app/photoswipe.js rename app/ui/client/views/app/{photoswipe.html => photoswipeContent.html} (95%) create mode 100644 app/ui/client/views/app/photoswipeContent.ts rename app/ui/{index.js => index.ts} (100%) diff --git a/app/ui-master/client/main.html b/app/ui-master/client/main.html index f226be71093..587453a4193 100644 --- a/app/ui-master/client/main.html +++ b/app/ui-master/client/main.html @@ -35,7 +35,6 @@ {{/if}} {{/unless}} {{ CustomScriptLoggedIn }} - {{> photoswipe}} {{/unless}} {{else}} {{> loading}} diff --git a/app/ui/client/index.js b/app/ui/client/index.ts similarity index 94% rename from app/ui/client/index.js rename to app/ui/client/index.ts index c30bf6f3437..c577eba5195 100644 --- a/app/ui/client/index.js +++ b/app/ui/client/index.ts @@ -17,7 +17,6 @@ import './views/app/pageCustomContainer.html'; import './views/app/roomSearch.html'; import './views/app/secretURL.html'; import './views/app/userSearch.html'; -import './views/app/photoswipe.html'; import './views/cmsPage'; import './views/404/roomNotFound'; import './views/app/burger'; @@ -25,7 +24,7 @@ import './views/app/home'; import './views/app/roomSearch'; import './views/app/secretURL'; import './views/app/invite'; -import './views/app/photoswipe'; +import './views/app/photoswipeContent.ts'; // without the *.ts extension, *.html gets loaded first import './components/icon'; import './components/table.html'; import './components/table'; diff --git a/app/ui/client/views/app/photoswipe.js b/app/ui/client/views/app/photoswipe.js deleted file mode 100644 index e42ccdfe949..00000000000 --- a/app/ui/client/views/app/photoswipe.js +++ /dev/null @@ -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) }
${ escapeHTML(item.description) }`; - 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')); -}); diff --git a/app/ui/client/views/app/photoswipe.html b/app/ui/client/views/app/photoswipeContent.html similarity index 95% rename from app/ui/client/views/app/photoswipe.html rename to app/ui/client/views/app/photoswipeContent.html index d4ee792610e..0eb80ecbd7f 100644 --- a/app/ui/client/views/app/photoswipe.html +++ b/app/ui/client/views/app/photoswipeContent.html @@ -1,5 +1,3 @@ - - \ No newline at end of file + diff --git a/app/ui/client/views/app/photoswipeContent.ts b/app/ui/client/views/app/photoswipeContent.ts new file mode 100644 index 00000000000..7e6ee33b917 --- /dev/null +++ b/app/ui/client/views/app/photoswipeContent.ts @@ -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 => { + 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 | null = null; + +const initGallery = async (items: Slide[], options: PhotoSwipeUiDefault.Options): Promise => { + 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 ?? '') }
+ ${ escapeHTML(item.description ?? '') } + `; + 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')); +}); diff --git a/app/ui/index.js b/app/ui/index.ts similarity index 100% rename from app/ui/index.js rename to app/ui/index.ts diff --git a/package-lock.json b/package-lock.json index 3cd9701c99a..d76d900ad17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10161,6 +10161,12 @@ "@types/node": "*" } }, + "@types/photoswipe": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@types/photoswipe/-/photoswipe-4.1.2.tgz", + "integrity": "sha512-HA9TtCAQKToldgxRiyJ1DbsElg/cQV/SQ8COVjqIqghjy60Zxfh78E1WiFotthquqkS86nz13Za9wEbToe0svQ==", + "dev": true + }, "@types/pretty-hrtime": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz", diff --git a/package.json b/package.json index 2e1aac5947f..5a7bcf01fbf 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "@types/node-rsa": "^1.1.1", "@types/nodemailer": "^6.4.2", "@types/parseurl": "^1.3.1", + "@types/photoswipe": "^4.1.2", "@types/psl": "^1.1.0", "@types/react": "^17.0.32", "@types/react-dom": "^17.0.10",