74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|||
|
|
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||
|
|
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||
|
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||
|
|
import { useRoomMediaTimeline } from '../../hooks/useRoomMediaTimeline';
|
||
|
|
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||
|
|
import { Lightbox, toLightboxItems } from './MediaGallery';
|
||
|
|
|
||
|
|
// How many media pages to walk back looking for the clicked event before
|
||
|
|
// settling for a single-item viewer.
|
||
|
|
const MAX_SEARCH_PAGES = 6;
|
||
|
|
|
||
|
|
type RoomMediaLightboxProps = {
|
||
|
|
room: Room;
|
||
|
|
eventId: string;
|
||
|
|
onClose: () => void;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* [Gitea #219] The room timeline's image viewer is the gallery lightbox,
|
||
|
|
* opened at the clicked event: same dark backdrop, sender + date, 1/N counter,
|
||
|
|
* ←/→ across the room's media, zoom keys, download and "jump to message".
|
||
|
|
*
|
||
|
|
* Items come from the same detached media timeline the gallery uses (#163).
|
||
|
|
* It starts at the live end, so an older image may not be loaded yet: we page
|
||
|
|
* back a bounded number of times looking for it and meanwhile show the clicked
|
||
|
|
* image on its own, so the viewer never waits on the network to open.
|
||
|
|
*/
|
||
|
|
export function RoomMediaLightbox({ room, eventId, onClose }: RoomMediaLightboxProps) {
|
||
|
|
const mx = useMatrixClient();
|
||
|
|
const useAuthentication = useMediaAuthentication();
|
||
|
|
const { navigateRoom } = useRoomNavigate();
|
||
|
|
const { events, loadMore, loading, canLoadMore } = useRoomMediaTimeline(mx, room);
|
||
|
|
|
||
|
|
const items = useMemo(() => toLightboxItems(room, events), [room, events]);
|
||
|
|
const index = items.findIndex((it) => it.eventId === eventId);
|
||
|
|
|
||
|
|
const pagesRef = useRef(0);
|
||
|
|
useEffect(() => {
|
||
|
|
if (index !== -1 || loading || !canLoadMore || pagesRef.current >= MAX_SEARCH_PAGES) return;
|
||
|
|
pagesRef.current += 1;
|
||
|
|
loadMore();
|
||
|
|
}, [index, loading, canLoadMore, loadMore]);
|
||
|
|
|
||
|
|
// Until the event is in the loaded window, show just the clicked media.
|
||
|
|
const fallbackItems = useMemo(() => {
|
||
|
|
const ev: MatrixEvent | undefined = room.findEventById(eventId) ?? undefined;
|
||
|
|
return ev ? toLightboxItems(room, [ev]) : [];
|
||
|
|
}, [room, eventId]);
|
||
|
|
|
||
|
|
const handleJump = useCallback(
|
||
|
|
(id: string) => {
|
||
|
|
onClose();
|
||
|
|
navigateRoom(room.roomId, id);
|
||
|
|
},
|
||
|
|
[onClose, navigateRoom, room.roomId],
|
||
|
|
);
|
||
|
|
|
||
|
|
const found = index !== -1;
|
||
|
|
const shownItems = found ? items : fallbackItems;
|
||
|
|
if (shownItems.length === 0) return null;
|
||
|
|
return (
|
||
|
|
<Lightbox
|
||
|
|
// Remount when the full list arrives so the index and zoom reset cleanly.
|
||
|
|
key={found ? 'room' : 'single'}
|
||
|
|
items={shownItems}
|
||
|
|
initialIndex={found ? index : 0}
|
||
|
|
useAuthentication={useAuthentication}
|
||
|
|
onClose={onClose}
|
||
|
|
onJump={handleJump}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|