feat(media): timeline images open the gallery lightbox at that event (#219)
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
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
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user