Skip to content

Commit

Permalink
feat: ImageGallery provider
Browse files Browse the repository at this point in the history
  • Loading branch information
juliajforesti committed Oct 30, 2023
1 parent 38a7e85 commit bd4fed4
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 56 deletions.
14 changes: 5 additions & 9 deletions apps/meteor/client/components/ImageGallery/ImageGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<SwiperRef>(null);
const [, setSwiperInst] = useState<SwiperClass>();

const { isLoading, currentSlide, loadMore, images, onClose } = useImageGallery();

if (isLoading) {
return <ImageGalleryLoader />;
return <ImageGalleryLoader onClose={onClose} />;
}

return createPortal(
Expand Down
13 changes: 11 additions & 2 deletions apps/meteor/client/components/ImageGallery/ImageGalleryLoader.tsx
Original file line number Diff line number Diff line change
@@ -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(
<ModalBackdrop display='flex' justifyContent='center'>
<IconButton icon='cross' aria-label='Close gallery' className={closeButtonStyle} onClick={onClose} />
<Throbber />
</ModalBackdrop>,
document.body,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string[]>([]);
const [currentSlide, setCurrentSlide] = useState<number>();
const [imageUrl, setImageUrl] = useState<string>();

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));
Expand All @@ -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,
};
};
13 changes: 13 additions & 0 deletions apps/meteor/client/contexts/ImageGalleryContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createContext } from 'react';

export type ImageGalleryContextValue = {
imageUrl: string;
isOpen: boolean;
onClose: () => void;
};

export const ImageGalleryContext = createContext<ImageGalleryContextValue>({
imageUrl: '',
isOpen: false,
onClose: () => undefined,
});
47 changes: 47 additions & 0 deletions apps/meteor/client/providers/ImageGalleryProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<string>();

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 (
<ImageGalleryContext.Provider value={{ imageUrl: imageUrl || '', isOpen: !!imageUrl, onClose: () => setImageUrl(undefined) }}>
{children}
{!!imageUrl && <ImageGallery />}
</ImageGalleryContext.Provider>
);
};

export default ImageGalleryProvider;
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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('');
Expand Down Expand Up @@ -56,23 +53,20 @@ const RoomFilesWithData = () => {
const isDeletionAllowed = useMessageDeletionIsAllowed(room._id, uid);

return (
<>
{isOpen && <ImageGallery {...imageGalleryProps} />}
<RoomFiles
rid={room._id}
loading={phase === AsyncStatePhase.LOADING}
type={type}
text={text}
loadMoreItems={loadMoreItems}
setType={setType}
setText={handleTextChange}
filesItems={filesItems}
total={totalItemCount}
onClickClose={closeTab}
onClickDelete={handleDelete}
isDeletionAllowed={isDeletionAllowed}
/>
</>
<RoomFiles
rid={room._id}
loading={phase === AsyncStatePhase.LOADING}
type={type}
text={text}
loadMoreItems={loadMoreItems}
setType={setType}
setText={handleTextChange}
filesItems={filesItems}
total={totalItemCount}
onClickClose={closeTab}
onClickDelete={handleDelete}
isDeletionAllowed={isDeletionAllowed}
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const FileItem = ({ fileData, isDeletionAllowed, onClickDelete }) => {
const { _id, name, url, uploadedAt, ts, type, typeGroup, style, className, user } = fileData;

return (
<Box display='flex' p={12} borderRadius='x4' style={style} className={[className, hoverClass, 'gallery-item']}>
<Box display='flex' p={12} borderRadius='x4' style={style} className={[className, hoverClass]}>
{typeGroup === 'image' ? (
<ImageItem url={url} name={name} username={user?.username} timestamp={format(uploadedAt)} />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ type ImageItemProps = {
};
const ImageItem = ({ url, name, timestamp, username }: ImageItemProps) => {
return (
<Box minWidth={0} className='gallery-item' title={name} display='flex' flexGrow={1} flexShrink={1}>
<Box minWidth={0} className='gallery-item-container' title={name} display='flex' flexGrow={1} flexShrink={1}>
{url && <Avatar size='x48' url={url} className='gallery-item' />}
<Box mis={8} flexShrink={1} overflow='hidden' className='gallery-item'>
<Box mis={8} flexShrink={1} overflow='hidden' className='gallery-item' cursor='default'>
{name && (
<Box withTruncatedText color='default' fontScale='p2m' className='gallery-item'>
{name}
Expand Down
10 changes: 4 additions & 6 deletions apps/meteor/client/views/room/providers/RoomProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -104,17 +103,16 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => {
};
}, [rid, subscribed]);

const { isOpen, ...imageGalleryProps } = useImageGallery(rid);

if (!pseudoRoom) {
return isSuccess && !room ? <RoomNotFound /> : <RoomSkeleton />;
}

return (
<RoomContext.Provider value={context}>
<RoomToolboxProvider>
{isOpen && <ImageGallery {...imageGalleryProps} />}
<ComposerPopupProvider room={pseudoRoom}>{children}</ComposerPopupProvider>
<ImageGalleryProvider>
<ComposerPopupProvider room={pseudoRoom}>{children}</ComposerPopupProvider>
</ImageGalleryProvider>
</RoomToolboxProvider>
</RoomContext.Provider>
);
Expand Down

0 comments on commit bd4fed4

Please sign in to comment.