1
0
Fork 0
forked from Mirrors/gomuks

web/roomlist: add option to hide invite avatars

This commit is contained in:
Tulir Asokan 2025-03-09 17:18:27 +02:00
parent 86843d61f6
commit ef05bc71f9
7 changed files with 46 additions and 15 deletions

View file

@ -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)
}

View file

@ -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

View file

@ -55,6 +55,7 @@ export interface RoomListEntry {
unread_notifications: number
unread_highlights: number
marked_unread: boolean
is_invite?: boolean
}
export interface GCSettings {

View file

@ -65,6 +65,12 @@ export const preferences = {
allowedContexts: anyContext,
defaultValue: true,
}),
show_invite_avatars: new Preference<boolean>({
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<boolean>({
displayName: "Code block line wrap",
description: "Whether to wrap long lines in code blocks instead of scrolling horizontally.",

View file

@ -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) {
<img
loading="lazy"
className="avatar room-avatar"
src={getRoomAvatarThumbnailURL(room)}
src={getRoomAvatarThumbnailURL(room, undefined, hideAvatar)}
alt=""
/>
</div>
@ -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<HTMLDivElement>()
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}
</div>
}

View file

@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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 <div className="room-list-wrapper">
<div className="room-search-wrapper">
@ -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}
/>,
)}
</div>

View file

@ -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 <div className="room-view preview">
<div className="preview-inner">
{invite?.invited_by && !invite.dm_user_id ? <div className="inviter-info">
<img
className="small avatar"
onClick={use(LightboxContext)}
src={getAvatarThumbnailURL(invite.invited_by, invite.inviter_profile)}
src={getAvatarThumbnailURL(invite.invited_by, invite.inviter_profile, noAvatarPreview)}
data-full-src={getAvatarURL(invite.invited_by, invite.inviter_profile)}
alt=""
/>
@ -102,7 +105,8 @@ const RoomPreview = ({ roomID, via, alias, invite }: RoomPreviewProps) => {
<h2 className="room-name">{name}</h2>
<img
// this is a big avatar (120px), use full resolution
src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID })}
src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID }, undefined, false, noAvatarPreview)}
data-full-src={getRoomAvatarURL(invite ?? summary ?? { room_id: roomID })}
className="large avatar"
onClick={use(LightboxContext)}
alt=""