// Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useEffect, useRef } from 'react'; import moment from 'moment'; import type { ItemClickEvent } from './types/ItemClickEvent'; import type { LocalizerType } from '../../../types/Util'; import type { MediaItemType } from '../../../types/MediaItem'; import type { SaveAttachmentActionCreatorType } from '../../../state/ducks/conversations'; import { AttachmentSection } from './AttachmentSection'; import { EmptyState } from './EmptyState'; import { Tabs } from '../../Tabs'; import { groupMediaItemsByDate } from './groupMediaItemsByDate'; import { missingCaseError } from '../../../util/missingCaseError'; import { usePrevious } from '../../../hooks/usePrevious'; import type { AttachmentType } from '../../../types/Attachment'; enum TabViews { Media = 'Media', Documents = 'Documents', } export type Props = { conversationId: string; documents: ReadonlyArray; i18n: LocalizerType; haveOldestMedia: boolean; haveOldestDocument: boolean; loading: boolean; initialLoad: (id: string) => unknown; loadMoreMedia: (id: string) => unknown; loadMoreDocuments: (id: string) => unknown; media: ReadonlyArray; saveAttachment: SaveAttachmentActionCreatorType; showLightbox: (options: { attachment: AttachmentType; messageId: string; }) => void; }; const MONTH_FORMAT = 'MMMM YYYY'; function MediaSection({ documents, i18n, loading, media, saveAttachment, showLightbox, type, }: Pick< Props, 'documents' | 'i18n' | 'loading' | 'media' | 'saveAttachment' | 'showLightbox' > & { type: 'media' | 'documents' }): JSX.Element { const mediaItems = type === 'media' ? media : documents; if (!mediaItems || mediaItems.length === 0) { if (loading) { return
; } const label = (() => { switch (type) { case 'media': return i18n('icu:mediaEmptyState'); case 'documents': return i18n('icu:documentsEmptyState'); default: throw missingCaseError(type); } })(); return ; } const now = Date.now(); const sections = groupMediaItemsByDate(now, mediaItems).map(section => { const first = section.mediaItems[0]; const { message } = first; const date = moment(message.receivedAtMs || message.receivedAt); function getHeader(): string { switch (section.type) { case 'yearMonth': return date.format(MONTH_FORMAT); case 'today': return i18n('icu:today'); case 'yesterday': return i18n('icu:yesterday'); case 'thisWeek': return i18n('icu:thisWeek'); case 'thisMonth': return i18n('icu:thisMonth'); default: throw missingCaseError(section); } } const header = getHeader(); return ( { switch (event.type) { case 'documents': { saveAttachment(event.attachment, event.message.sentAt); break; } case 'media': { showLightbox({ attachment: event.attachment, messageId: event.message.id, }); break; } default: throw new TypeError(`Unknown attachment type: '${event.type}'`); } }} /> ); }); return
{sections}
; } export function MediaGallery({ conversationId, documents, haveOldestDocument, haveOldestMedia, i18n, initialLoad, loading, loadMoreDocuments, loadMoreMedia, media, saveAttachment, showLightbox, }: Props): JSX.Element { const focusRef = useRef(null); const scrollObserverRef = useRef(null); const intersectionObserver = useRef(null); const loadingRef = useRef(false); const tabViewRef = useRef(TabViews.Media); useEffect(() => { focusRef.current?.focus(); }, []); useEffect(() => { if ( media.length > 0 || documents.length > 0 || haveOldestDocument || haveOldestMedia ) { return; } initialLoad(conversationId); loadingRef.current = true; }, [ conversationId, haveOldestDocument, haveOldestMedia, initialLoad, media, documents, ]); const previousLoading = usePrevious(loading, loading); if (previousLoading && !loading) { loadingRef.current = false; } useEffect(() => { if (loading || !scrollObserverRef.current) { return; } intersectionObserver.current?.disconnect(); intersectionObserver.current = null; intersectionObserver.current = new IntersectionObserver( (entries: ReadonlyArray) => { if (loadingRef.current) { return; } const entry = entries.find( item => item.target === scrollObserverRef.current ); if (entry && entry.intersectionRatio > 0) { if (tabViewRef.current === TabViews.Media) { if (!haveOldestMedia) { loadMoreMedia(conversationId); loadingRef.current = true; } } else { // eslint-disable-next-line no-lonely-if if (!haveOldestDocument) { loadMoreDocuments(conversationId); loadingRef.current = true; } } } } ); intersectionObserver.current.observe(scrollObserverRef.current); return () => { intersectionObserver.current?.disconnect(); intersectionObserver.current = null; }; }, [ conversationId, haveOldestDocument, haveOldestMedia, loading, loadMoreDocuments, loadMoreMedia, ]); return (
{({ selectedTab }) => { tabViewRef.current = selectedTab === TabViews.Media ? TabViews.Media : TabViews.Documents; return (
{selectedTab === TabViews.Media && ( )} {selectedTab === TabViews.Documents && ( )}
); }}
); }