diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md index 06b9743b9..8012dc4f6 100644 --- a/LOTUS_FEATURES.md +++ b/LOTUS_FEATURES.md @@ -1107,7 +1107,7 @@ A toggle in **Settings → Privacy** switches between sending `m.read` (public r `MediaGallery.tsx` — a right-side drawer for browsing room media. - Four tabs: **Images**, **Videos**, **Audio**, **Files** (each with a live count) -- **Images/Videos** — a month-grouped grid; tiles decrypt on demand (lazy, near-viewport), open a keyboard-navigable **lightbox** (←/→/Esc, prev/next) +- **Images/Videos** — a month-grouped grid; tiles decrypt on demand (lazy, near-viewport), open a keyboard-navigable **lightbox** (←/→/Esc, prev/next). Each grid tile has a hover/focus **download** button, and the lightbox header has a **Download** action — both reuse the shared `FileDownloadButton` (full-resolution source, decrypts E2EE media client-side, spinner/✓/retry states), so images and videos can be saved without jumping to the message. On touch (no-hover) devices the tile download button stays visible. - **Audio** — voice messages + audio files (`m.audio`) with an inline player (reuses `AudioContent`: **waveform scrubbing** for voice messages, play/seek/**speed control**; decrypts on play) - **Files** — name/size/sender rows with download - **Jump to message** — a "Go to message" action on file rows, audio rows, and in the lightbox navigates the timeline to the source event (`useRoomNavigate`) and closes the drawer diff --git a/src/app/features/room/MediaGallery.css.ts b/src/app/features/room/MediaGallery.css.ts index 4fd571092..e760b3032 100644 --- a/src/app/features/room/MediaGallery.css.ts +++ b/src/app/features/room/MediaGallery.css.ts @@ -52,8 +52,38 @@ export const MediaGalleryGrid = style({ gap: config.space.S100, }); +// Wraps a tile + its floating download button so the download control is a +// sibling of the tile + {downloadUrl && ( +
+
)} - + ); } @@ -783,6 +839,9 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) { // Guard before incrementing: skipped tiles must not consume a slot if (!thumbMxc) return null; const idx = flatIdx++; + // Full-resolution source for download (not the thumb). + const fullMxc: string | undefined = c.file?.url ?? c.url; + const bodyStr: string = c.body ?? ''; return ( setLightboxIndex(idx)} + downloadUrl={fullMxc} + downloadEncInfo={isEnc ? c.file : undefined} + downloadMimeType={info?.mimetype} + downloadFilename={mediaFilename(bodyStr, info?.mimetype)} /> ); })}