diff --git a/web/src/api/media.ts b/web/src/api/media.ts index 028ab6f..5d5ee67 100644 --- a/web/src/api/media.ts +++ b/web/src/api/media.ts @@ -78,7 +78,7 @@ function getFallbackCharacter(from: unknown, idx: number): string { return Array.from(from.slice(0, (idx + 1) * 2))[idx]?.toUpperCase().toWellFormed() ?? "" } -export const getAvatarURL = (userID: UserID, content?: UserProfile | null): string | undefined => { +export const getAvatarURL = (userID: UserID, content?: UserProfile | null, thumbnail = false): string | undefined => { const fallbackCharacter = getFallbackCharacter(content?.displayname, 0) || getFallbackCharacter(userID, 1) const backgroundColor = getUserColor(userID) const [server, mediaID] = parseMXC(content?.avatar_file?.url ?? content?.avatar_url) @@ -87,7 +87,12 @@ export const getAvatarURL = (userID: UserID, content?: UserProfile | null): stri } const encrypted = !!content?.avatar_file const fallback = `${backgroundColor}:${fallbackCharacter}` - return `_gomuks/media/${server}/${mediaID}?encrypted=${encrypted}&fallback=${encodeURIComponent(fallback)}` + const url = `_gomuks/media/${server}/${mediaID}?encrypted=${encrypted}&fallback=${encodeURIComponent(fallback)}` + return thumbnail ? `${url}&thumbnail=avatar` : url +} + +export const getAvatarThumbnailURL = (userID: UserID, content?: UserProfile | null): string | undefined => { + return getAvatarURL(userID, content, true) } interface RoomForAvatarURL { @@ -98,9 +103,15 @@ interface RoomForAvatarURL { avatar_url?: ContentURI } -export const getRoomAvatarURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => { +export const getRoomAvatarURL = ( + room: RoomForAvatarURL, avatarOverride?: ContentURI, thumbnail = false, +): string | undefined => { return getAvatarURL(room.dm_user_id ?? room.room_id, { displayname: room.name, avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url, - }) + }, thumbnail) +} + +export const getRoomAvatarThumbnailURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => { + return getRoomAvatarURL(room, avatarOverride, true) } diff --git a/web/src/api/statestore/main.ts b/web/src/api/statestore/main.ts index c430fa7..5e808a4 100644 --- a/web/src/api/statestore/main.ts +++ b/web/src/api/statestore/main.ts @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -import { getAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL } from "@/api/media.ts" import { Preferences, getLocalStoragePreferences, getPreferenceProxy } from "@/api/types/preferences" import { CustomEmojiPack, parseCustomEmojiPack } from "@/util/emoji" import { NonNullCachedEventDispatcher } from "@/util/eventdispatcher.ts" @@ -469,7 +469,7 @@ export class StateStore { body = body.slice(0, 350) + " […]" } const memberEvt = room.getStateEvent("m.room.member", evt.sender) - const icon = `${getAvatarURL(evt.sender, memberEvt?.content)}&image_auth=${this.imageAuthToken}` + const icon = `${getAvatarThumbnailURL(evt.sender, memberEvt?.content)}&image_auth=${this.imageAuthToken}` const roomName = room.meta.current.name ?? "Unnamed room" const senderName = memberEvt?.content.displayname ?? evt.sender const title = senderName === roomName ? senderName : `${senderName} (${roomName})` diff --git a/web/src/ui/composer/Autocompleter.tsx b/web/src/ui/composer/Autocompleter.tsx index 432d327..0d9eff5 100644 --- a/web/src/ui/composer/Autocompleter.tsx +++ b/web/src/ui/composer/Autocompleter.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { JSX, RefObject, use, useEffect } from "react" -import { getAvatarURL, getMediaURL } from "@/api/media.ts" +import { getAvatarThumbnailURL, getMediaURL } from "@/api/media.ts" import { AutocompleteMemberEntry, RoomStateStore, useCustomEmojis } from "@/api/statestore" import { Emoji, emojiToMarkdown, useSortedAndFilteredEmojis } from "@/util/emoji" import { escapeMarkdown } from "@/util/markdown.ts" @@ -138,7 +138,7 @@ const userFuncs = { {user.displayName} diff --git a/web/src/ui/composer/TypingNotifications.tsx b/web/src/ui/composer/TypingNotifications.tsx index 10812ce..2f689f9 100644 --- a/web/src/ui/composer/TypingNotifications.tsx +++ b/web/src/ui/composer/TypingNotifications.tsx @@ -15,7 +15,7 @@ // along with this program. If not, see . import { JSX, use } from "react" import { PulseLoader } from "react-spinners" -import { getAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL } from "@/api/media.ts" import { useMultipleRoomMembers, useRoomTyping } from "@/api/statestore" import { humanJoin } from "@/util/join.ts" import { getDisplayname } from "@/util/validation.ts" @@ -40,7 +40,7 @@ const TypingNotifications = () => { key={sender} className="small avatar" loading="lazy" - src={getAvatarURL(sender, member)} + src={getAvatarThumbnailURL(sender, member)} alt="" />) memberNames.push(getDisplayname(sender, member)) diff --git a/web/src/ui/modal/Lightbox.tsx b/web/src/ui/modal/Lightbox.tsx index a8e395a..abf4dd2 100644 --- a/web/src/ui/modal/Lightbox.tsx +++ b/web/src/ui/modal/Lightbox.tsx @@ -36,7 +36,7 @@ const LightboxWrapper = ({ children }: { children: React.ReactNode }) => { return } params = { - src: target.src, + src: target.getAttribute("data-full-src") ?? target.src, alt: target.alt, } setParams(params) diff --git a/web/src/ui/rightpanel/MemberList.tsx b/web/src/ui/rightpanel/MemberList.tsx index a88853e..8e41e27 100644 --- a/web/src/ui/rightpanel/MemberList.tsx +++ b/web/src/ui/rightpanel/MemberList.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React, { use, useState } from "react" -import { getAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL } from "@/api/media.ts" import { MemDBEvent, MemberEventContent } from "@/api/types" import { getDisplayname } from "@/util/validation.ts" import ClientContext from "../ClientContext.ts" @@ -33,7 +33,7 @@ const MemberRow = ({ evt, onClick }: MemberRowProps) => { return
diff --git a/web/src/ui/rightpanel/UserInfo.tsx b/web/src/ui/rightpanel/UserInfo.tsx index 87b5ea0..dd10c81 100644 --- a/web/src/ui/rightpanel/UserInfo.tsx +++ b/web/src/ui/rightpanel/UserInfo.tsx @@ -61,6 +61,7 @@ const UserInfo = ({ userID }: UserInfoProps) => { className="avatar-loader" /> : . import { JSX, memo, use } from "react" -import { getRoomAvatarURL } from "@/api/media.ts" +import { getRoomAvatarThumbnailURL } from "@/api/media.ts" import type { RoomListEntry } from "@/api/statestore" import type { MemDBEvent, MemberEventContent } from "@/api/types" import useContentVisibility from "@/util/contentvisibility.ts" @@ -63,7 +63,7 @@ function renderEntry(room: RoomListEntry) {
diff --git a/web/src/ui/roomlist/Space.tsx b/web/src/ui/roomlist/Space.tsx index daeb35c..6cdea09 100644 --- a/web/src/ui/roomlist/Space.tsx +++ b/web/src/ui/roomlist/Space.tsx @@ -15,7 +15,7 @@ // along with this program. If not, see . import React from "react" import Client from "@/api/client.ts" -import { getRoomAvatarURL } from "@/api/media.ts" +import { getRoomAvatarThumbnailURL } from "@/api/media.ts" import type { RoomID } from "@/api/types" import { useEventAsState } from "@/util/eventdispatcher.ts" import UnreadCount from "./UnreadCount.tsx" @@ -37,7 +37,7 @@ const Space = ({ roomID, client, onClick, isActive, onClickUnread }: SpaceProps) } return
- {room.name} + {room.name}
} diff --git a/web/src/ui/roomview/RoomPreview.tsx b/web/src/ui/roomview/RoomPreview.tsx index dab8003..fd6700d 100644 --- a/web/src/ui/roomview/RoomPreview.tsx +++ b/web/src/ui/roomview/RoomPreview.tsx @@ -15,7 +15,7 @@ // along with this program. If not, see . import { use, useEffect, useState } from "react" import { ScaleLoader } from "react-spinners" -import { getAvatarURL, getRoomAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL, getAvatarURL, getRoomAvatarURL } from "@/api/media.ts" import { InvitedRoomStore } from "@/api/statestore/invitedroom.ts" import { RoomID, RoomSummary } from "@/api/types" import { getDisplayname, getServerName } from "@/util/validation.ts" @@ -90,7 +90,8 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => { @@ -100,6 +101,7 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => { : null}

{name}

. import { use } from "react" -import { getRoomAvatarURL } from "@/api/media.ts" +import { getRoomAvatarThumbnailURL, getRoomAvatarURL } from "@/api/media.ts" import { RoomStateStore } from "@/api/statestore" import { useEventAsState } from "@/util/eventdispatcher.ts" import MainScreenContext from "../MainScreenContext.ts" @@ -48,7 +48,8 @@ const RoomViewHeader = ({ room }: RoomViewHeaderProps) => { diff --git a/web/src/ui/settings/SettingsView.tsx b/web/src/ui/settings/SettingsView.tsx index 67ec3c7..fb8cc44 100644 --- a/web/src/ui/settings/SettingsView.tsx +++ b/web/src/ui/settings/SettingsView.tsx @@ -16,7 +16,7 @@ import { Suspense, lazy, use, useCallback, useRef, useState } from "react" import { ScaleLoader } from "react-spinners" import Client from "@/api/client.ts" -import { getRoomAvatarURL } from "@/api/media.ts" +import { getRoomAvatarThumbnailURL, getRoomAvatarURL } from "@/api/media.ts" import { RoomStateStore, usePreferences } from "@/api/statestore" import { Preference, @@ -355,7 +355,8 @@ const SettingsView = ({ room }: SettingsViewProps) => { diff --git a/web/src/ui/timeline/ReadReceipts.tsx b/web/src/ui/timeline/ReadReceipts.tsx index c4d3436..b342211 100644 --- a/web/src/ui/timeline/ReadReceipts.tsx +++ b/web/src/ui/timeline/ReadReceipts.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { use } from "react" -import { getAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL } from "@/api/media.ts" import { RoomStateStore, useMultipleRoomMembers, useReadReceipts } from "@/api/statestore" import { EventID } from "@/api/types" import { humanJoin } from "@/util/join.ts" @@ -37,7 +37,7 @@ const ReadReceipts = ({ room, eventID }: { room: RoomStateStore, eventID: EventI key={userID} className="small avatar" loading="lazy" - src={getAvatarURL(userID, member)} + src={getAvatarThumbnailURL(userID, member)} alt="" /> }) diff --git a/web/src/ui/timeline/ReplyBody.tsx b/web/src/ui/timeline/ReplyBody.tsx index 65c20c8..93f7c63 100644 --- a/web/src/ui/timeline/ReplyBody.tsx +++ b/web/src/ui/timeline/ReplyBody.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { use } from "react" -import { getAvatarURL, getUserColorIndex } from "@/api/media.ts" +import { getAvatarThumbnailURL, getUserColorIndex } from "@/api/media.ts" import { RoomStateStore, useRoomEvent, useRoomMember } from "@/api/statestore" import type { EventID, MemDBEvent, MemberEventContent } from "@/api/types" import { getDisplayname } from "@/util/validation.ts" @@ -124,7 +124,7 @@ export const ReplyBody = ({ diff --git a/web/src/ui/timeline/TimelineEvent.tsx b/web/src/ui/timeline/TimelineEvent.tsx index f4a7ae8..0c6e711 100644 --- a/web/src/ui/timeline/TimelineEvent.tsx +++ b/web/src/ui/timeline/TimelineEvent.tsx @@ -15,7 +15,7 @@ // along with this program. If not, see . import React, { JSX, use, useState } from "react" import { createPortal } from "react-dom" -import { getAvatarURL, getMediaURL, getUserColorIndex } from "@/api/media.ts" +import { getAvatarThumbnailURL, getMediaURL, getUserColorIndex } from "@/api/media.ts" import { useRoomMember } from "@/api/statestore" import { MemDBEvent, MemberEventContent, UnreadType } from "@/api/types" import { isMobileDevice } from "@/util/ismobile.ts" @@ -233,7 +233,7 @@ const TimelineEvent = ({ evt, prevEvt, disableMenu, smallReplies, isFocused }: T } diff --git a/web/src/ui/timeline/content/MemberBody.tsx b/web/src/ui/timeline/content/MemberBody.tsx index e86ce34..d3011e2 100644 --- a/web/src/ui/timeline/content/MemberBody.tsx +++ b/web/src/ui/timeline/content/MemberBody.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React, { use } from "react" -import { getAvatarURL } from "@/api/media.ts" +import { getAvatarThumbnailURL, getAvatarURL } from "@/api/media.ts" import { MemberEventContent, UserID } from "@/api/types" import { LightboxContext } from "../../modal" import EventContentProps from "./props.ts" @@ -25,7 +25,8 @@ function useChangeDescription( const targetAvatar = @@ -59,7 +60,8 @@ function useChangeDescription( className="small avatar" loading="lazy" height={16} - src={getAvatarURL(target, prevContent)} + src={getAvatarThumbnailURL(target, prevContent)} + data-full-src={getAvatarURL(target, prevContent)} onClick={use(LightboxContext)!} alt="" /> to {targetAvatar} diff --git a/web/src/ui/timeline/content/RoomAvatarBody.tsx b/web/src/ui/timeline/content/RoomAvatarBody.tsx index 7878bbb..3b1e225 100644 --- a/web/src/ui/timeline/content/RoomAvatarBody.tsx +++ b/web/src/ui/timeline/content/RoomAvatarBody.tsx @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import { JSX, use } from "react" -import { getRoomAvatarURL } from "@/api/media.ts" +import { getRoomAvatarThumbnailURL, getRoomAvatarURL } from "@/api/media.ts" import { ContentURI, RoomAvatarEventContent } from "@/api/types" import { ensureString } from "@/util/validation.ts" import { LightboxContext } from "../../modal" @@ -31,7 +31,8 @@ const RoomAvatarBody = ({ event, sender, room }: EventContentProps) => { className="small avatar" loading="lazy" height={16} - src={getRoomAvatarURL(room.meta.current, url)} + src={getRoomAvatarThumbnailURL(room.meta.current, url)} + data-full-src={getRoomAvatarURL(room.meta.current, url)} onClick={openLightbox} alt="" />