Revert "Member avatars without canvas" (#10057

* Revert "Apply more general fix for base avatar regressions (#10045)"

This reverts commit 371a3c0d36.

* Revert "Fix layout and visual regressions around default avatars (#10031)"

This reverts commit 0d1fce37b2.

* Revert "Member avatars without canvas (#9990)"

This reverts commit a8aa4de4b4.

* Update snapshots
This commit is contained in:
Michael Telatynski 2023-02-02 10:22:19 +00:00 committed by GitHub
parent 43e7870d92
commit 21c3967010
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 352 additions and 823 deletions

View file

@ -1,6 +1,8 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2015, 2016, 2018, 2019, 2020, 2023 The Matrix.org Foundation C.I.C.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,46 +17,38 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { CSSProperties, useCallback, useContext, useEffect, useState } from "react";
import React, { useCallback, useContext, useEffect, useState } from "react";
import classNames from "classnames";
import { ResizeMethod } from "matrix-js-sdk/src/@types/partials";
import { ClientEvent } from "matrix-js-sdk/src/client";
import { SyncState } from "matrix-js-sdk/src/sync";
import * as AvatarLogic from "../../../Avatar";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import RoomContext from "../../../contexts/RoomContext";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { useTypedEventEmitter } from "../../../hooks/useEventEmitter";
import { toPx } from "../../../utils/units";
import { _t } from "../../../languageHandler";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
interface IProps {
/** The name (first initial used as default) */
name: string;
/** ID for generating hash colours */
idName?: string;
/** onHover title text */
title?: string;
/** highest priority of them all, shortcut to set in urls[0] */
url?: string;
/** [highest_priority, ... , lowest_priority] */
urls?: string[];
name: string; // The name (first initial used as default)
idName?: string; // ID for generating hash colours
title?: string; // onHover title text
url?: string; // highest priority of them all, shortcut to set in urls[0]
urls?: string[]; // [highest_priority, ... , lowest_priority]
width?: number;
height?: number;
/** @deprecated not actually used */
// XXX: resizeMethod not actually used.
resizeMethod?: ResizeMethod;
/** true to add default url */
defaultToInitialLetter?: boolean;
onClick?: React.ComponentPropsWithoutRef<typeof AccessibleTooltipButton>["onClick"];
defaultToInitialLetter?: boolean; // true to add default url
onClick?: React.MouseEventHandler;
inputRef?: React.RefObject<HTMLImageElement & HTMLSpanElement>;
className?: string;
tabIndex?: number;
style?: CSSProperties;
}
const calculateUrls = (url: string | undefined, urls: string[] | undefined, lowBandwidth: boolean): string[] => {
const calculateUrls = (url: string, urls: string[], lowBandwidth: boolean): string[] => {
// work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, ...props.urls ]
@ -72,26 +66,11 @@ const calculateUrls = (url: string | undefined, urls: string[] | undefined, lowB
return Array.from(new Set(_urls));
};
/**
* Hook for cycling through a changing set of images.
*
* The set of images is updated whenever `url` or `urls` change, the user's
* `lowBandwidth` preference changes, or the client reconnects.
*
* Returns `[imageUrl, onError]`. When `onError` is called, the next image in
* the set will be displayed.
*/
const useImageUrl = ({
url,
urls,
}: {
url: string | undefined;
urls: string[] | undefined;
}): [string | undefined, () => void] => {
const useImageUrl = ({ url, urls }): [string, () => void] => {
// Since this is a hot code path and the settings store can be slow, we
// use the cached lowBandwidth value from the room context if it exists
const roomContext = useContext(RoomContext);
const lowBandwidth = roomContext.lowBandwidth;
const lowBandwidth = roomContext ? roomContext.lowBandwidth : SettingsStore.getValue("lowBandwidth");
const [imageUrls, setUrls] = useState<string[]>(calculateUrls(url, urls, lowBandwidth));
const [urlsIndex, setIndex] = useState<number>(0);
@ -106,10 +85,10 @@ const useImageUrl = ({
}, [url, JSON.stringify(urls)]); // eslint-disable-line react-hooks/exhaustive-deps
const cli = useContext(MatrixClientContext);
const onClientSync = useCallback((syncState: SyncState, prevState: SyncState | null) => {
const onClientSync = useCallback((syncState, prevState) => {
// Consider the client reconnected if there is no error with syncing.
// This means the state could be RECONNECTING, SYNCING, PREPARED or CATCHUP.
const reconnected = syncState !== SyncState.Error && prevState !== syncState;
const reconnected = syncState !== "ERROR" && prevState !== syncState;
if (reconnected) {
setIndex(0);
}
@ -129,25 +108,46 @@ const BaseAvatar: React.FC<IProps> = (props) => {
urls,
width = 40,
height = 40,
resizeMethod = "crop", // eslint-disable-line @typescript-eslint/no-unused-vars
defaultToInitialLetter = true,
onClick,
inputRef,
className,
style: parentStyle,
resizeMethod: _unused, // to keep it from being in `otherProps`
...otherProps
} = props;
const style = {
...parentStyle,
width: toPx(width),
height: toPx(height),
};
const [imageUrl, onError] = useImageUrl({ url, urls });
if (!imageUrl && defaultToInitialLetter && name) {
const avatar = <TextAvatar name={name} idName={idName} width={width} height={height} title={title} />;
const initialLetter = AvatarLogic.getInitialLetter(name);
const textNode = (
<span
className="mx_BaseAvatar_initial"
aria-hidden="true"
style={{
fontSize: toPx(width * 0.65),
width: toPx(width),
lineHeight: toPx(height),
}}
>
{initialLetter}
</span>
);
const imgNode = (
<img
className="mx_BaseAvatar_image"
src={AvatarLogic.defaultAvatarUrlForString(idName || name)}
alt=""
title={title}
onError={onError}
style={{
width: toPx(width),
height: toPx(height),
}}
aria-hidden="true"
data-testid="avatar-img"
/>
);
if (onClick) {
return (
@ -159,9 +159,9 @@ const BaseAvatar: React.FC<IProps> = (props) => {
className={classNames("mx_BaseAvatar", className)}
onClick={onClick}
inputRef={inputRef}
style={style}
>
{avatar}
{textNode}
{imgNode}
</AccessibleButton>
);
} else {
@ -170,10 +170,10 @@ const BaseAvatar: React.FC<IProps> = (props) => {
className={classNames("mx_BaseAvatar", className)}
ref={inputRef}
{...otherProps}
style={style}
role="presentation"
>
{avatar}
{textNode}
{imgNode}
</span>
);
}
@ -187,7 +187,10 @@ const BaseAvatar: React.FC<IProps> = (props) => {
src={imageUrl}
onClick={onClick}
onError={onError}
style={style}
style={{
width: toPx(width),
height: toPx(height),
}}
title={title}
alt={_t("Avatar")}
inputRef={inputRef}
@ -201,7 +204,10 @@ const BaseAvatar: React.FC<IProps> = (props) => {
className={classNames("mx_BaseAvatar mx_BaseAvatar_image", className)}
src={imageUrl}
onError={onError}
style={style}
style={{
width: toPx(width),
height: toPx(height),
}}
title={title}
alt=""
ref={inputRef}
@ -214,31 +220,3 @@ const BaseAvatar: React.FC<IProps> = (props) => {
export default BaseAvatar;
export type BaseAvatarType = React.FC<IProps>;
const TextAvatar: React.FC<{
name: string;
idName?: string;
width: number;
height: number;
title?: string;
}> = ({ name, idName, width, height, title }) => {
const initialLetter = AvatarLogic.getInitialLetter(name);
return (
<span
className="mx_BaseAvatar_image mx_BaseAvatar_initial"
aria-hidden="true"
style={{
backgroundColor: AvatarLogic.getColorForString(idName || name),
width: toPx(width),
height: toPx(height),
fontSize: toPx(width * 0.65),
lineHeight: toPx(height),
}}
title={title}
data-testid="avatar-img"
>
{initialLetter}
</span>
);
};

View file

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016, 2019 - 2023 The Matrix.org Foundation C.I.C.
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2019 - 2022 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -25,7 +26,6 @@ import { mediaFromMxc } from "../../../customisations/Media";
import { CardContext } from "../right_panel/context";
import UserIdentifierCustomisations from "../../../customisations/UserIdentifier";
import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile";
import { ViewUserPayload } from "../../../dispatcher/payloads/ViewUserPayload";
interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url"> {
member: RoomMember | null;
@ -33,13 +33,14 @@ interface IProps extends Omit<React.ComponentProps<typeof BaseAvatar>, "name" |
width: number;
height: number;
resizeMethod?: ResizeMethod;
/** Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser` */
// The onClick to give the avatar
onClick?: React.MouseEventHandler;
// Whether the onClick of the avatar should be overridden to dispatch `Action.ViewUser`
viewUserOnClick?: boolean;
pushUserOnClick?: boolean;
title?: string;
style?: React.CSSProperties;
/** true to deny `useOnlyCurrentProfiles` usage. Default false. */
forceHistorical?: boolean;
style?: any;
forceHistorical?: boolean; // true to deny `useOnlyCurrentProfiles` usage. Default false.
hideTitle?: boolean;
}
@ -76,8 +77,8 @@ export default function MemberAvatar({
if (!title) {
title =
UserIdentifierCustomisations.getDisplayUserIdentifier!(member.userId, {
roomId: member.roomId,
UserIdentifierCustomisations.getDisplayUserIdentifier(member?.userId ?? "", {
roomId: member?.roomId ?? "",
}) ?? fallbackUserId;
}
}
@ -87,6 +88,7 @@ export default function MemberAvatar({
{...props}
width={width}
height={height}
resizeMethod={resizeMethod}
name={name ?? ""}
title={hideTitle ? undefined : title}
idName={member?.userId ?? fallbackUserId}
@ -94,9 +96,9 @@ export default function MemberAvatar({
onClick={
viewUserOnClick
? () => {
dis.dispatch<ViewUserPayload>({
dis.dispatch({
action: Action.ViewUser,
member: propsMember || undefined,
member: propsMember,
push: card.isCard,
});
}

View file

@ -109,8 +109,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
}
private onRoomAvatarClick = (): void => {
const avatarMxc = this.props.room?.getMxcAvatarUrl();
const avatarUrl = avatarMxc ? mediaFromMxc(avatarMxc).srcHttp : null;
const avatarUrl = Avatar.avatarUrlForRoom(this.props.room, null, null, null);
const params = {
src: avatarUrl,
name: this.props.room.name,