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:
@@ -173,7 +173,7 @@ function getThumbMxc(mEvent: MatrixEvent): string | undefined {
|
||||
|
||||
// ── Lightbox ──────────────────────────────────────────────────────────────────
|
||||
|
||||
type LightboxItem = {
|
||||
export type LightboxItem = {
|
||||
mxcUrl: string;
|
||||
encInfo?: IEncryptedFile;
|
||||
mimeType?: string;
|
||||
@@ -184,6 +184,35 @@ type LightboxItem = {
|
||||
eventId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Images + videos of a media timeline as lightbox slots. Shared with the
|
||||
* timeline's viewer (#219) so both open the same items in the same order.
|
||||
*/
|
||||
export function toLightboxItems(room: Room, events: MatrixEvent[]): LightboxItem[] {
|
||||
return events
|
||||
.filter((ev) => {
|
||||
const c = ev.getContent();
|
||||
if (c.msgtype !== MsgType.Image && c.msgtype !== MsgType.Video) return false;
|
||||
// Match the grid's guard exactly: tiles without a thumb are not rendered,
|
||||
// so they must not occupy a lightbox slot either.
|
||||
return !!getThumbMxc(ev);
|
||||
})
|
||||
.map((ev) => {
|
||||
const c = ev.getContent();
|
||||
const isEnc = !!c.file;
|
||||
return {
|
||||
mxcUrl: c.file?.url ?? c.url ?? '',
|
||||
encInfo: isEnc ? c.file : undefined,
|
||||
mimeType: c.info?.mimetype,
|
||||
msgtype: c.msgtype as MsgType.Image | MsgType.Video,
|
||||
body: c.body ?? '',
|
||||
sender: getSenderName(room, ev.getSender() ?? ''),
|
||||
ts: ev.getTs(),
|
||||
eventId: ev.getId() ?? '',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function LightboxMedia({
|
||||
item,
|
||||
useAuthentication,
|
||||
@@ -280,7 +309,7 @@ function LightboxMedia({
|
||||
);
|
||||
}
|
||||
|
||||
function Lightbox({
|
||||
export function Lightbox({
|
||||
items,
|
||||
initialIndex,
|
||||
useAuthentication,
|
||||
@@ -765,28 +794,7 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {
|
||||
}, [canLoadMore, loading, handleLoadMore]);
|
||||
|
||||
// Lightbox items (images + videos, flat)
|
||||
const lightboxItems: LightboxItem[] = events
|
||||
.filter((ev) => {
|
||||
const c = ev.getContent();
|
||||
if (c.msgtype !== MsgType.Image && c.msgtype !== MsgType.Video) return false;
|
||||
// Match the grid's guard exactly: tiles without a thumb are not rendered,
|
||||
// so they must not occupy a lightbox slot either.
|
||||
return !!getThumbMxc(ev);
|
||||
})
|
||||
.map((ev) => {
|
||||
const c = ev.getContent();
|
||||
const isEnc = !!c.file;
|
||||
return {
|
||||
mxcUrl: c.file?.url ?? c.url ?? '',
|
||||
encInfo: isEnc ? c.file : undefined,
|
||||
mimeType: c.info?.mimetype,
|
||||
msgtype: c.msgtype as MsgType.Image | MsgType.Video,
|
||||
body: c.body ?? '',
|
||||
sender: getSenderName(room, ev.getSender() ?? ''),
|
||||
ts: ev.getTs(),
|
||||
eventId: ev.getId() ?? '',
|
||||
};
|
||||
});
|
||||
const lightboxItems: LightboxItem[] = toLightboxItems(room, events);
|
||||
|
||||
// Per-tab counts for the tab labels (single pass over the loaded media)
|
||||
const tabCounts = useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user