Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit c092bc2

Browse files
committed
Revert "Member avatars without canvas (#9990)"
This reverts commit a8aa4de.
1 parent a07f503 commit c092bc2

File tree

22 files changed

+311
-806
lines changed

22 files changed

+311
-806
lines changed

res/css/views/rooms/_BasicMessageComposer.pcss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ limitations under the License.
7878
min-width: $font-16px; /* ensure the avatar is not compressed */
7979
height: $font-16px;
8080
margin-inline-end: 0.24rem;
81-
background: var(--avatar-background);
81+
background: var(--avatar-background), $background;
8282
color: $avatar-initial-color;
8383
background-repeat: no-repeat;
8484
background-size: $font-16px;

src/Avatar.ts

Lines changed: 23 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2015, 2016, 2023 The Matrix.org Foundation C.I.C.
2+
Copyright 2015, 2016 OpenMarket Ltd
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -24,19 +24,16 @@ import DMRoomMap from "./utils/DMRoomMap";
2424
import { mediaFromMxc } from "./customisations/Media";
2525
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
2626

27-
const DEFAULT_COLORS: Readonly<string[]> = ["#0DBD8B", "#368bd6", "#ac3ba8"];
28-
2927
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
3028
export function avatarUrlForMember(
31-
member: RoomMember | null | undefined,
29+
member: RoomMember,
3230
width: number,
3331
height: number,
3432
resizeMethod: ResizeMethod,
3533
): string {
36-
let url: string | undefined;
37-
const mxcUrl = member?.getMxcAvatarUrl();
38-
if (mxcUrl) {
39-
url = mediaFromMxc(mxcUrl).getThumbnailOfSourceHttp(width, height, resizeMethod);
34+
let url: string;
35+
if (member?.getMxcAvatarUrl()) {
36+
url = mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
4037
}
4138
if (!url) {
4239
// member can be null here currently since on invites, the JS SDK
@@ -47,17 +44,6 @@ export function avatarUrlForMember(
4744
return url;
4845
}
4946

50-
export function getMemberAvatar(
51-
member: RoomMember | null | undefined,
52-
width: number,
53-
height: number,
54-
resizeMethod: ResizeMethod,
55-
): string | undefined {
56-
const mxcUrl = member?.getMxcAvatarUrl();
57-
if (!mxcUrl) return undefined;
58-
return mediaFromMxc(mxcUrl).getThumbnailOfSourceHttp(width, height, resizeMethod);
59-
}
60-
6147
export function avatarUrlForUser(
6248
user: Pick<User, "avatarUrl">,
6349
width: number,
@@ -100,10 +86,18 @@ function urlForColor(color: string): string {
10086
// hard to install a listener here, even if there were a clear event to listen to
10187
const colorToDataURLCache = new Map<string, string>();
10288

103-
export function defaultAvatarUrlForString(s: string | undefined): string {
89+
export function defaultAvatarUrlForString(s: string): string {
10490
if (!s) return ""; // XXX: should never happen but empirically does by evidence of a rageshake
105-
106-
const color = getColorForString(s);
91+
const defaultColors = ["#0DBD8B", "#368bd6", "#ac3ba8"];
92+
let total = 0;
93+
for (let i = 0; i < s.length; ++i) {
94+
total += s.charCodeAt(i);
95+
}
96+
const colorIndex = total % defaultColors.length;
97+
// overwritten color value in custom themes
98+
const cssVariable = `--avatar-background-colors_${colorIndex}`;
99+
const cssValue = document.body.style.getPropertyValue(cssVariable);
100+
const color = cssValue || defaultColors[colorIndex];
107101
let dataUrl = colorToDataURLCache.get(color);
108102
if (!dataUrl) {
109103
// validate color as this can come from account_data
@@ -118,23 +112,13 @@ export function defaultAvatarUrlForString(s: string | undefined): string {
118112
return dataUrl;
119113
}
120114

121-
export function getColorForString(input: string): string {
122-
const charSum = [...input].reduce((s, c) => s + c.charCodeAt(0), 0);
123-
const index = charSum % DEFAULT_COLORS.length;
124-
125-
// overwritten color value in custom themes
126-
const cssVariable = `--avatar-background-colors_${index}`;
127-
const cssValue = document.body.style.getPropertyValue(cssVariable);
128-
return cssValue || DEFAULT_COLORS[index]!;
129-
}
130-
131115
/**
132116
* returns the first (non-sigil) character of 'name',
133117
* converted to uppercase
134118
* @param {string} name
135119
* @return {string} the first letter
136120
*/
137-
export function getInitialLetter(name: string): string | undefined {
121+
export function getInitialLetter(name: string): string {
138122
if (!name) {
139123
// XXX: We should find out what causes the name to sometimes be falsy.
140124
console.trace("`name` argument to `getInitialLetter` not supplied");
@@ -150,20 +134,19 @@ export function getInitialLetter(name: string): string | undefined {
150134
}
151135

152136
// rely on the grapheme cluster splitter in lodash so that we don't break apart compound emojis
153-
return split(name, "", 1)[0]!.toUpperCase();
137+
return split(name, "", 1)[0].toUpperCase();
154138
}
155139

156140
export function avatarUrlForRoom(
157-
room: Room | undefined,
141+
room: Room,
158142
width: number,
159143
height: number,
160144
resizeMethod?: ResizeMethod,
161145
): string | null {
162146
if (!room) return null; // null-guard
163147

164-
const mxcUrl = room.getMxcAvatarUrl();
165-
if (mxcUrl) {
166-
return mediaFromMxc(mxcUrl).getThumbnailOfSourceHttp(width, height, resizeMethod);
148+
if (room.getMxcAvatarUrl()) {
149+
return mediaFromMxc(room.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
167150
}
168151

169152
// space rooms cannot be DMs so skip the rest
@@ -176,9 +159,8 @@ export function avatarUrlForRoom(
176159

177160
// If there are only two members in the DM use the avatar of the other member
178161
const otherMember = room.getAvatarFallbackMember();
179-
const otherMemberMxc = otherMember?.getMxcAvatarUrl();
180-
if (otherMemberMxc) {
181-
return mediaFromMxc(otherMemberMxc).getThumbnailOfSourceHttp(width, height, resizeMethod);
162+
if (otherMember?.getMxcAvatarUrl()) {
163+
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
182164
}
183165
return null;
184166
}

src/components/views/avatars/BaseAvatar.tsx

Lines changed: 51 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
/*
2+
Copyright 2015, 2016 OpenMarket Ltd
3+
Copyright 2018 New Vector Ltd
24
Copyright 2019 Michael Telatynski <[email protected]>
3-
Copyright 2015, 2016, 2018, 2019, 2020, 2023 The Matrix.org Foundation C.I.C.
5+
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
46
57
Licensed under the Apache License, Version 2.0 (the "License");
68
you may not use this file except in compliance with the License.
@@ -19,41 +21,34 @@ import React, { useCallback, useContext, useEffect, useState } from "react";
1921
import classNames from "classnames";
2022
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
2123
import { ClientEvent } from "matrix-js-sdk/src/client";
22-
import { SyncState } from "matrix-js-sdk/src/sync";
2324

2425
import * as AvatarLogic from "../../../Avatar";
26+
import SettingsStore from "../../../settings/SettingsStore";
2527
import AccessibleButton from "../elements/AccessibleButton";
2628
import RoomContext from "../../../contexts/RoomContext";
2729
import MatrixClientContext from "../../../contexts/MatrixClientContext";
2830
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
2931
import { toPx } from "../../../utils/units";
3032
import { _t } from "../../../languageHandler";
31-
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
3233

3334
interface IProps {
34-
/** The name (first initial used as default) */
35-
name: string;
36-
/** ID for generating hash colours */
37-
idName?: string;
38-
/** onHover title text */
39-
title?: string;
40-
/** highest priority of them all, shortcut to set in urls[0] */
41-
url?: string;
42-
/** [highest_priority, ... , lowest_priority] */
43-
urls?: string[];
35+
name: string; // The name (first initial used as default)
36+
idName?: string; // ID for generating hash colours
37+
title?: string; // onHover title text
38+
url?: string; // highest priority of them all, shortcut to set in urls[0]
39+
urls?: string[]; // [highest_priority, ... , lowest_priority]
4440
width?: number;
4541
height?: number;
46-
/** @deprecated not actually used */
42+
// XXX: resizeMethod not actually used.
4743
resizeMethod?: ResizeMethod;
48-
/** true to add default url */
49-
defaultToInitialLetter?: boolean;
50-
onClick?: React.ComponentPropsWithoutRef<typeof AccessibleTooltipButton>["onClick"];
44+
defaultToInitialLetter?: boolean; // true to add default url
45+
onClick?: React.MouseEventHandler;
5146
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
5247
className?: string;
5348
tabIndex?: number;
5449
}
5550

56-
const calculateUrls = (url: string | undefined, urls: string[] | undefined, lowBandwidth: boolean): string[] => {
51+
const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): string[] => {
5752
// work out the full set of urls to try to load. This is formed like so:
5853
// imageUrls: [ props.url, ...props.urls ]
5954

@@ -71,26 +66,11 @@ const calculateUrls = (url: string | undefined, urls: string[] | undefined, lowB
7166
return Array.from(new Set(_urls));
7267
};
7368

74-
/**
75-
* Hook for cycling through a changing set of images.
76-
*
77-
* The set of images is updated whenever `url` or `urls` change, the user's
78-
* `lowBandwidth` preference changes, or the client reconnects.
79-
*
80-
* Returns `[imageUrl, onError]`. When `onError` is called, the next image in
81-
* the set will be displayed.
82-
*/
83-
const useImageUrl = ({
84-
url,
85-
urls,
86-
}: {
87-
url: string | undefined;
88-
urls: string[] | undefined;
89-
}): [string | undefined, () => void] => {
69+
const useImageUrl = ({ url, urls }): [string, () => void] => {
9070
// Since this is a hot code path and the settings store can be slow, we
9171
// use the cached lowBandwidth value from the room context if it exists
9272
const roomContext = useContext(RoomContext);
93-
const lowBandwidth = roomContext.lowBandwidth;
73+
const lowBandwidth = roomContext ? roomContext.lowBandwidth : SettingsStore.getValue("lowBandwidth");
9474

9575
const [imageUrls, setUrls] = useState<string[]>(calculateUrls(url, urls, lowBandwidth));
9676
const [urlsIndex, setIndex] = useState<number>(0);
@@ -105,10 +85,10 @@ const useImageUrl = ({
10585
}, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
10686

10787
const cli = useContext(MatrixClientContext);
108-
const onClientSync = useCallback((syncState: SyncState, prevState: SyncState | null) => {
88+
const onClientSync = useCallback((syncState, prevState) => {
10989
// Consider the client reconnected if there is no error with syncing.
11090
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
111-
const reconnected = syncState !== SyncState.Error && prevState !== syncState;
91+
const reconnected = syncState !== "ERROR" && prevState !== syncState;
11292
if (reconnected) {
11393
setIndex(0);
11494
}
@@ -128,18 +108,46 @@ const BaseAvatar: React.FC<IProps> = (props) => {
128108
urls,
129109
width = 40,
130110
height = 40,
111+
resizeMethod = "crop", // eslint-disable-line @typescript-eslint/no-unused-vars
131112
defaultToInitialLetter = true,
132113
onClick,
133114
inputRef,
134115
className,
135-
resizeMethod: _unused, // to keep it from being in `otherProps`
136116
...otherProps
137117
} = props;
138118

139119
const [imageUrl, onError] = useImageUrl({ url, urls });
140120

141121
if (!imageUrl && defaultToInitialLetter && name) {
142-
const avatar = <TextAvatar name={name} idName={idName} width={width} height={height} title={title} />;
122+
const initialLetter = AvatarLogic.getInitialLetter(name);
123+
const textNode = (
124+
<span
125+
className="mx_BaseAvatar_initial"
126+
aria-hidden="true"
127+
style={{
128+
fontSize: toPx(width * 0.65),
129+
width: toPx(width),
130+
lineHeight: toPx(height),
131+
}}
132+
>
133+
{initialLetter}
134+
</span>
135+
);
136+
const imgNode = (
137+
<img
138+
className="mx_BaseAvatar_image"
139+
src={AvatarLogic.defaultAvatarUrlForString(idName || name)}
140+
alt=""
141+
title={title}
142+
onError={onError}
143+
style={{
144+
width: toPx(width),
145+
height: toPx(height),
146+
}}
147+
aria-hidden="true"
148+
data-testid="avatar-img"
149+
/>
150+
);
143151

144152
if (onClick) {
145153
return (
@@ -151,12 +159,9 @@ const BaseAvatar: React.FC<IProps> = (props) => {
151159
className={classNames("mx_BaseAvatar", className)}
152160
onClick={onClick}
153161
inputRef={inputRef}
154-
style={{
155-
width: toPx(width),
156-
height: toPx(height),
157-
}}
158162
>
159-
{avatar}
163+
{textNode}
164+
{imgNode}
160165
</AccessibleButton>
161166
);
162167
} else {
@@ -165,13 +170,10 @@ const BaseAvatar: React.FC<IProps> = (props) => {
165170
className={classNames("mx_BaseAvatar", className)}
166171
ref={inputRef}
167172
{...otherProps}
168-
style={{
169-
width: toPx(width),
170-
height: toPx(height),
171-
}}
172173
role="presentation"
173174
>
174-
{avatar}
175+
{textNode}
176+
{imgNode}
175177
</span>
176178
);
177179
}
@@ -218,31 +220,3 @@ const BaseAvatar: React.FC<IProps> = (props) => {
218220

219221
export default BaseAvatar;
220222
export type BaseAvatarType = React.FC<IProps>;
221-
222-
const TextAvatar: React.FC<{
223-
name: string;
224-
idName?: string;
225-
width: number;
226-
height: number;
227-
title?: string;
228-
}> = ({ name, idName, width, height, title }) => {
229-
const initialLetter = AvatarLogic.getInitialLetter(name);
230-
231-
return (
232-
<span
233-
className="mx_BaseAvatar_image mx_BaseAvatar_initial"
234-
aria-hidden="true"
235-
style={{
236-
backgroundColor: AvatarLogic.getColorForString(idName || name),
237-
width: toPx(width),
238-
height: toPx(height),
239-
fontSize: toPx(width * 0.65),
240-
lineHeight: toPx(height),
241-
}}
242-
title={title}
243-
data-testid="avatar-img"
244-
>
245-
{initialLetter}
246-
</span>
247-
);
248-
};

0 commit comments

Comments
 (0)