CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
Clicking an image in the room or thread timeline now opens the same viewer the media gallery uses — dark backdrop, sender + date, 1/N counter, ←/→ across the room's media, +/-/0 and wheel/double-click zoom, download, jump to message — positioned at the clicked event. RoomMediaLightbox feeds it from the detached media timeline (#163); when the event isn't in the loaded window it pages back (bounded, 6 pages) and shows the clicked image alone meanwhile, so the viewer opens instantly. ImageContent gains onOpenViewer (RenderMessageContent passes onOpenImageViewer); its built-in viewer remains for stickers, search results, pins, notifications and avatars. Verified headless: click → 'Media viewer' dialog focused, counter 1/2, '+' → 120 %, Jump to message present, Esc closes; an older image 10 pages up → found at 10/45 after paging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
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}
|
|
/>
|
|
);
|
|
}
|