Revert "Member avatars without canvas" (#10057
* Revert "Apply more general fix for base avatar regressions (#10045)" This reverts commit371a3c0d36
. * Revert "Fix layout and visual regressions around default avatars (#10031)" This reverts commit0d1fce37b2
. * Revert "Member avatars without canvas (#9990)" This reverts commita8aa4de4b4
. * Update snapshots
This commit is contained in:
parent
43e7870d92
commit
21c3967010
27 changed files with 352 additions and 823 deletions
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue