From ef05bc71f9a7b9e6d3e6b03c8592456c5bd10f96 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 9 Mar 2025 17:18:27 +0200 Subject: [PATCH] web/roomlist: add option to hide invite avatars --- web/src/api/media.ts | 32 +++++++++++++++----- web/src/api/statestore/invitedroom.ts | 1 + web/src/api/statestore/main.ts | 1 + web/src/api/types/preferences/preferences.ts | 6 ++++ web/src/ui/roomlist/Entry.tsx | 9 +++--- web/src/ui/roomlist/RoomList.tsx | 4 ++- web/src/ui/roomview/RoomPreview.tsx | 8 +++-- 7 files changed, 46 insertions(+), 15 deletions(-) diff --git a/web/src/api/media.ts b/web/src/api/media.ts index 5d5ee67..d6f12d3 100644 --- a/web/src/api/media.ts +++ b/web/src/api/media.ts @@ -78,11 +78,16 @@ 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, thumbnail = false): string | undefined => { +export const getAvatarURL = ( + userID: UserID, + content?: UserProfile | null, + thumbnail = false, + forceFallback = 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) - if (!mediaID) { + if (!mediaID || forceFallback) { return makeFallbackAvatar(backgroundColor, fallbackCharacter) } const encrypted = !!content?.avatar_file @@ -91,8 +96,12 @@ export const getAvatarURL = (userID: UserID, content?: UserProfile | null, thumb return thumbnail ? `${url}&thumbnail=avatar` : url } -export const getAvatarThumbnailURL = (userID: UserID, content?: UserProfile | null): string | undefined => { - return getAvatarURL(userID, content, true) +export const getAvatarThumbnailURL = ( + userID: UserID, + content?: UserProfile | null, + forceFallback = false, +): string | undefined => { + return getAvatarURL(userID, content, true, forceFallback) } interface RoomForAvatarURL { @@ -104,14 +113,21 @@ interface RoomForAvatarURL { } export const getRoomAvatarURL = ( - room: RoomForAvatarURL, avatarOverride?: ContentURI, thumbnail = false, + room: RoomForAvatarURL, + avatarOverride?: ContentURI, + thumbnail = false, + forceFallback = false, ): string | undefined => { return getAvatarURL(room.dm_user_id ?? room.room_id, { displayname: room.name, avatar_url: avatarOverride ?? room.avatar ?? room.avatar_url, - }, thumbnail) + }, thumbnail, forceFallback) } -export const getRoomAvatarThumbnailURL = (room: RoomForAvatarURL, avatarOverride?: ContentURI): string | undefined => { - return getRoomAvatarURL(room, avatarOverride, true) +export const getRoomAvatarThumbnailURL = ( + room: RoomForAvatarURL, + avatarOverride?: ContentURI, + forceFallback = false, +): string | undefined => { + return getRoomAvatarURL(room, avatarOverride, true, forceFallback) } diff --git a/web/src/api/statestore/invitedroom.ts b/web/src/api/statestore/invitedroom.ts index a4b2a87..86b8ec2 100644 --- a/web/src/api/statestore/invitedroom.ts +++ b/web/src/api/statestore/invitedroom.ts @@ -45,6 +45,7 @@ export class InvitedRoomStore implements RoomListEntry, RoomSummary { readonly invited_by?: UserID readonly inviter_profile?: MemberEventContent readonly is_direct: boolean + readonly is_invite = true constructor(public readonly meta: DBInvitedRoom, parent: StateStore) { this.room_id = meta.room_id diff --git a/web/src/api/statestore/main.ts b/web/src/api/statestore/main.ts index 804d118..08b3422 100644 --- a/web/src/api/statestore/main.ts +++ b/web/src/api/statestore/main.ts @@ -55,6 +55,7 @@ export interface RoomListEntry { unread_notifications: number unread_highlights: number marked_unread: boolean + is_invite?: boolean } export interface GCSettings { diff --git a/web/src/api/types/preferences/preferences.ts b/web/src/api/types/preferences/preferences.ts index 05b4c1f..7259c9f 100644 --- a/web/src/api/types/preferences/preferences.ts +++ b/web/src/api/types/preferences/preferences.ts @@ -65,6 +65,12 @@ export const preferences = { allowedContexts: anyContext, defaultValue: true, }), + show_invite_avatars: new Preference({ + displayName: "Show avatars in invites", + description: "If disabled, the avatar of the room or invitee will not be shown in the invite view.", + allowedContexts: anyGlobalContext, + defaultValue: true, + }), code_block_line_wrap: new Preference({ displayName: "Code block line wrap", description: "Whether to wrap long lines in code blocks instead of scrolling horizontally.", diff --git a/web/src/ui/roomlist/Entry.tsx b/web/src/ui/roomlist/Entry.tsx index 020517b..c765c98 100644 --- a/web/src/ui/roomlist/Entry.tsx +++ b/web/src/ui/roomlist/Entry.tsx @@ -29,6 +29,7 @@ export interface RoomListEntryProps { room: RoomListEntry isActive: boolean hidden: boolean + hideAvatar?: boolean } function getPreviewText(evt?: MemDBEvent, senderMemberEvt?: MemDBEvent | null): [string, JSX.Element | null] { @@ -57,7 +58,7 @@ function getPreviewText(evt?: MemDBEvent, senderMemberEvt?: MemDBEvent | null): return ["", null] } -function renderEntry(room: RoomListEntry) { +function renderEntry(room: RoomListEntry, hideAvatar: boolean | undefined) { const [previewText, croppedPreviewText] = getPreviewText(room.preview_event, room.preview_sender) return <> @@ -65,7 +66,7 @@ function renderEntry(room: RoomListEntry) { @@ -77,7 +78,7 @@ function renderEntry(room: RoomListEntry) { } -const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => { +const Entry = ({ room, isActive, hidden, hideAvatar }: RoomListEntryProps) => { const [isVisible, divRef] = useContentVisibility() const openModal = use(ModalContext) const mainScreen = use(MainScreenContext) @@ -105,7 +106,7 @@ const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => { onContextMenu={onContextMenu} data-room-id={room.room_id} > - {isVisible ? renderEntry(room) : null} + {isVisible ? renderEntry(room, hideAvatar) : null} } diff --git a/web/src/ui/roomlist/RoomList.tsx b/web/src/ui/roomlist/RoomList.tsx index c83be1d..cc80423 100644 --- a/web/src/ui/roomlist/RoomList.tsx +++ b/web/src/ui/roomlist/RoomList.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, useCallback, useRef, useState } from "react" -import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts } from "@/api/statestore" +import { RoomListFilter, Space as SpaceStore, SpaceUnreadCounts, usePreference } from "@/api/statestore" import type { RoomID } from "@/api/types" import { useEventAsState } from "@/util/eventdispatcher.ts" import reverseMap from "@/util/reversemap.ts" @@ -103,6 +103,7 @@ const RoomList = ({ activeRoomID, space }: RoomListProps) => { } } + const showInviteAvatars = usePreference(client.store, null, "show_invite_avatars") const roomListFilter = client.store.roomListFilterFunc return
@@ -145,6 +146,7 @@ const RoomList = ({ activeRoomID, space }: RoomListProps) => { isActive={room.room_id === activeRoomID} hidden={roomListFilter ? !roomListFilter(room) : false} room={room} + hideAvatar={room.is_invite && !showInviteAvatars} />, )}
diff --git a/web/src/ui/roomview/RoomPreview.tsx b/web/src/ui/roomview/RoomPreview.tsx index fd6700d..71682f0 100644 --- a/web/src/ui/roomview/RoomPreview.tsx +++ b/web/src/ui/roomview/RoomPreview.tsx @@ -16,6 +16,7 @@ import { use, useEffect, useState } from "react" import { ScaleLoader } from "react-spinners" import { getAvatarThumbnailURL, getAvatarURL, getRoomAvatarURL } from "@/api/media.ts" +import { usePreference } from "@/api/statestore/hooks.ts" import { InvitedRoomStore } from "@/api/statestore/invitedroom.ts" import { RoomID, RoomSummary } from "@/api/types" import { getDisplayname, getServerName } from "@/util/validation.ts" @@ -84,13 +85,15 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => { const name = summary?.name ?? summary?.canonical_alias ?? invite?.name ?? invite?.canonical_alias ?? alias ?? roomID const memberCount = summary?.num_joined_members || null const topic = summary?.topic ?? invite?.topic ?? "" + const showInviteAvatars = usePreference(client.store, null, "show_invite_avatars") + const noAvatarPreview = invite && !showInviteAvatars return
{invite?.invited_by && !invite.dm_user_id ?
@@ -102,7 +105,8 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => {

{name}