diff --git a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx index 6c1238812b8f..1fb42876784c 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGallery.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGallery.tsx @@ -13,6 +13,7 @@ import 'swiper/modules/keyboard/keyboard.min.css'; import 'swiper/modules/zoom/zoom.min.css'; import ImageGalleryLoader from './ImageGalleryLoader'; +import { useImageGallery } from './hooks/useImageGallery'; const swiperStyle = css` .swiper { @@ -83,19 +84,14 @@ const swiperStyle = css` } `; -type ImageGalleryProps = { - images: string[]; - isLoading: boolean; - loadMore: () => void; - currentSlide: number | undefined; - onClose: () => void; -}; -const ImageGallery = ({ images, isLoading, loadMore, currentSlide, onClose }: ImageGalleryProps) => { +const ImageGallery = () => { const swiperRef = useRef(null); const [, setSwiperInst] = useState(); + const { isLoading, currentSlide, loadMore, images, onClose } = useImageGallery(); + if (isLoading) { - return ; + return ; } return createPortal( diff --git a/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx index 588276d17d83..a495f345194e 100644 --- a/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx +++ b/apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx @@ -1,10 +1,19 @@ -import { ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; +import { css } from '@rocket.chat/css-in-js'; +import { IconButton, ModalBackdrop, Throbber } from '@rocket.chat/fuselage'; import React from 'react'; import { createPortal } from 'react-dom'; -const ImageGalleryLoader = () => +const closeButtonStyle = css` + position: absolute; + z-index: 10; + top: 10px; + right: 10px; +`; + +const ImageGalleryLoader = ({ onClose }: { onClose: () => void }) => createPortal( + , document.body, diff --git a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts index fb8a7032106c..d7ba82dc9fc5 100644 --- a/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts +++ b/apps/meteor/client/components/ImageGallery/hooks/useImageGallery.ts @@ -1,27 +1,19 @@ -import { useState, useEffect, useMemo } from 'react'; +import { useState, useEffect, useMemo, useContext } from 'react'; -// import { useRoom } from '../views/room/contexts/RoomContext'; +import { ImageGalleryContext } from '../../../contexts/ImageGalleryContext'; import { useRecordList } from '../../../hooks/lists/useRecordList'; +import { useRoom } from '../../../views/room/contexts/RoomContext'; import { useFilesList } from '../../../views/room/contextualBar/RoomFiles/hooks/useFilesList'; -export const useImageGallery = (rid: string, imageSelector?: string, containerSelector?: string) => { +export const useImageGallery = () => { + const { _id: rid } = useRoom(); + const { imageUrl, onClose } = useContext(ImageGalleryContext); const [images, setImages] = useState([]); const [currentSlide, setCurrentSlide] = useState(); - const [imageUrl, setImageUrl] = useState(); const { filesList, loadMoreItems } = useFilesList(useMemo(() => ({ rid, type: 'image', text: '' }), [rid])); const { phase, items: filesItems } = useRecordList(filesList); - useEffect(() => { - const container = containerSelector && document.querySelector(containerSelector); - (container || document).addEventListener('click', (event: Event) => { - const target = event?.target as HTMLElement | null; - if (target?.classList.contains(imageSelector || 'gallery-item')) { - target.dataset.src ? setImageUrl(target.dataset.src) : setImageUrl((target as HTMLImageElement)?.src); - } - }); - }, [containerSelector, imageSelector]); - useEffect(() => { if (phase === 'resolved') { setImages(filesItems.map((item) => item.url || '').filter(Boolean)); @@ -30,11 +22,10 @@ export const useImageGallery = (rid: string, imageSelector?: string, containerSe }, [filesItems, phase, imageUrl]); return { - isOpen: !!imageUrl, images, isLoading: phase === 'loading' || currentSlide === undefined, loadMore: () => loadMoreItems(filesItems.length - (currentSlide || 0), filesItems.length + 5), currentSlide, - onClose: () => setImageUrl(undefined), + onClose, }; }; diff --git a/apps/meteor/client/contexts/ImageGalleryContext.ts b/apps/meteor/client/contexts/ImageGalleryContext.ts new file mode 100644 index 000000000000..38f53c3f975b --- /dev/null +++ b/apps/meteor/client/contexts/ImageGalleryContext.ts @@ -0,0 +1,13 @@ +import { createContext } from 'react'; + +export type ImageGalleryContextValue = { + imageUrl: string; + isOpen: boolean; + onClose: () => void; +}; + +export const ImageGalleryContext = createContext({ + imageUrl: '', + isOpen: false, + onClose: () => undefined, +}); diff --git a/apps/meteor/client/providers/ImageGalleryProvider.tsx b/apps/meteor/client/providers/ImageGalleryProvider.tsx new file mode 100644 index 000000000000..98e0811010a5 --- /dev/null +++ b/apps/meteor/client/providers/ImageGalleryProvider.tsx @@ -0,0 +1,47 @@ +import React, { type ReactNode, useEffect, useState } from 'react'; + +import ImageGallery from '../components/ImageGallery/ImageGallery'; +import { ImageGalleryContext } from '../contexts/ImageGalleryContext'; + +type ImageGalleryProviderProps = { + children: ReactNode; + imageSelector?: string; + containerSelector?: string; +}; + +const ImageGalleryProvider = ({ children, imageSelector, containerSelector }: ImageGalleryProviderProps) => { + const [imageUrl, setImageUrl] = useState(); + + useEffect(() => { + document.addEventListener('click', (event: Event) => { + const target = event?.target as HTMLElement | null; + + if (target?.classList.contains('gallery-item')) { + return setImageUrl(target.dataset.src || target?.parentElement?.parentElement?.querySelector('img')?.src); + } + + if (target?.classList.contains('gallery-item-container')) { + return setImageUrl((target.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + } + if ( + target?.classList.contains('gallery-item') && + target?.parentElement?.parentElement?.classList.contains('gallery-item-container') + ) { + return setImageUrl((target.parentElement.parentElement.querySelector('img.rcx-avatar__element') as HTMLImageElement | null)?.src); + } + + if (target?.classList.contains('rcx-avatar__element') && target?.parentElement?.classList.contains('gallery-item')) { + return setImageUrl((target as HTMLImageElement).src); + } + }); + }, [containerSelector, imageSelector]); + + return ( + setImageUrl(undefined) }}> + {children} + {!!imageUrl && } + + ); +}; + +export default ImageGalleryProvider; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js index 5cda35e33836..c30c4aeca41e 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/RoomFilesWithData.js @@ -3,8 +3,6 @@ import { useSetModal, useToastMessageDispatch, useUserId, useMethod, useTranslat import React, { useState, useCallback, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; -import ImageGallery from '../../../../components/ImageGallery/ImageGallery'; -import { useImageGallery } from '../../../../components/ImageGallery/hooks/useImageGallery'; import { useRecordList } from '../../../../hooks/lists/useRecordList'; import { AsyncStatePhase } from '../../../../hooks/useAsyncState'; import { useRoom } from '../../contexts/RoomContext'; @@ -22,7 +20,6 @@ const RoomFilesWithData = () => { const closeModal = useMutableCallback(() => setModal()); const dispatchToastMessage = useToastMessageDispatch(); const deleteFile = useMethod('deleteFileMessage'); - const { isOpen, ...imageGalleryProps } = useImageGallery(room._id, 'rcx-avatar__element', 'rcx-verticalbar__content'); const [type, setType] = useLocalStorage('file-list-type', 'all'); const [text, setText] = useState(''); @@ -56,23 +53,20 @@ const RoomFilesWithData = () => { const isDeletionAllowed = useMessageDeletionIsAllowed(room._id, uid); return ( - <> - {isOpen && } - - + ); }; diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js index 6e17bd1a7459..cfed9fabe4b1 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItem.js @@ -20,7 +20,7 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => { const { _id, name, url, uploadedAt, ts, type, typeGroup, style, className, user } = fileData; return ( - + {typeGroup === 'image' ? ( ) : ( diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx index c62b50fa3696..cd9222171c30 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/ImageItem.tsx @@ -9,9 +9,9 @@ type ImageItemProps = { }; const ImageItem = ({ url, name, timestamp, username }: ImageItemProps) => { return ( - + {url && } - + {name && ( {name} diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 8c5d215a045f..47d65562460a 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -6,12 +6,11 @@ import React, { useMemo, memo, useEffect, useCallback } from 'react'; import { ChatSubscription } from '../../../../app/models/client'; import { RoomHistoryManager } from '../../../../app/ui-utils/client'; import { UserAction } from '../../../../app/ui/client/lib/UserAction'; -import ImageGallery from '../../../components/ImageGallery'; -import { useImageGallery } from '../../../components/ImageGallery/hooks/useImageGallery'; import { useReactiveQuery } from '../../../hooks/useReactiveQuery'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; import { RoomManager } from '../../../lib/RoomManager'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; +import ImageGalleryProvider from '../../../providers/ImageGalleryProvider'; import RoomNotFound from '../RoomNotFound'; import RoomSkeleton from '../RoomSkeleton'; import { useRoomRolesManagement } from '../body/hooks/useRoomRolesManagement'; @@ -104,8 +103,6 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }; }, [rid, subscribed]); - const { isOpen, ...imageGalleryProps } = useImageGallery(rid); - if (!pseudoRoom) { return isSuccess && !room ? : ; } @@ -113,8 +110,9 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { return ( - {isOpen && } - {children} + + {children} + );