Apply strictNullChecks to src/components/views/avatars/* (#10254)

This commit is contained in:
Michael Telatyński 2023-02-28 10:25:36 +00:00 committed by GitHub
parent eca28ac2f3
commit ae5725b24c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 41 additions and 31 deletions

View file

@ -139,14 +139,18 @@ export function getInitialLetter(name: string): string | undefined {
export function avatarUrlForRoom(
room: Room | null,
width: number,
height: number,
width?: number,
height?: number,
resizeMethod?: ResizeMethod,
): string | null {
if (!room) return null; // null-guard
if (room.getMxcAvatarUrl()) {
return mediaFromMxc(room.getMxcAvatarUrl() || undefined).getThumbnailOfSourceHttp(width, height, resizeMethod);
const media = mediaFromMxc(room.getMxcAvatarUrl() ?? undefined);
if (width !== undefined && height !== undefined) {
return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
}
return media.srcHttp;
}
// space rooms cannot be DMs so skip the rest
@ -160,7 +164,11 @@ export function avatarUrlForRoom(
// If there are only two members in the DM use the avatar of the other member
const otherMember = room.getAvatarFallbackMember();
if (otherMember?.getMxcAvatarUrl()) {
return mediaFromMxc(otherMember.getMxcAvatarUrl()).getThumbnailOfSourceHttp(width, height, resizeMethod);
const media = mediaFromMxc(otherMember.getMxcAvatarUrl());
if (width !== undefined && height !== undefined) {
return media.getThumbnailOfSourceHttp(width, height, resizeMethod);
}
return media.srcHttp;
}
return null;
}

View file

@ -35,10 +35,10 @@ interface IProps {
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]
url?: string | null; // highest priority of them all, shortcut to set in urls[0]
urls?: string[]; // [highest_priority, ... , lowest_priority]
width?: number;
height?: number;
width: number;
height: number;
// XXX: resizeMethod not actually used.
resizeMethod?: ResizeMethod;
defaultToInitialLetter?: boolean; // true to add default url
@ -48,7 +48,7 @@ interface IProps {
tabIndex?: number;
}
const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): string[] => {
const calculateUrls = (url?: string | null, urls?: string[], lowBandwidth = false): string[] => {
// work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, ...props.urls ]
@ -66,7 +66,7 @@ const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): str
return Array.from(new Set(_urls));
};
const useImageUrl = ({ url, urls }: { url?: string; urls?: string[] }): [string, () => void] => {
const useImageUrl = ({ url, urls }: { url?: string | null; urls?: string[] }): [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);

View file

@ -62,7 +62,7 @@ enum Icon {
PresenceBusy = "BUSY",
}
function tooltipText(variant: Icon): string {
function tooltipText(variant: Icon): string | undefined {
switch (variant) {
case Icon.Globe:
return _t("This room is public");
@ -78,7 +78,7 @@ function tooltipText(variant: Icon): string {
}
export default class DecoratedRoomAvatar extends React.PureComponent<IProps, IState> {
private _dmUser: User;
private _dmUser: User | null;
private isUnmounted = false;
private isWatchingTimeline = false;
@ -103,11 +103,11 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
return joinRule === JoinRule.Public;
}
private get dmUser(): User {
private get dmUser(): User | null {
return this._dmUser;
}
private set dmUser(val: User) {
private set dmUser(val: User | null) {
const oldUser = this._dmUser;
this._dmUser = val;
if (oldUser && oldUser !== this._dmUser) {
@ -120,7 +120,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
}
}
private onRoomTimeline = (ev: MatrixEvent, room: Room | null): void => {
private onRoomTimeline = (ev: MatrixEvent, room?: Room): void => {
if (this.isUnmounted) return;
if (this.props.room.roomId !== room?.roomId) return;
@ -182,7 +182,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
public render(): React.ReactNode {
let badge: React.ReactNode;
if (this.props.displayBadge) {
if (this.props.displayBadge && this.state.notificationState) {
badge = (
<NotificationBadge
notification={this.state.notificationState}
@ -192,7 +192,7 @@ export default class DecoratedRoomAvatar extends React.PureComponent<IProps, ISt
);
}
let icon;
let icon: JSX.Element | undefined;
if (this.state.icon !== Icon.None) {
icon = (
<TextWithTooltip

View file

@ -65,7 +65,7 @@ export default function MemberAvatar({
const name = member?.name ?? fallbackUserId;
let title: string | undefined = props.title;
let imageUrl: string | undefined;
let imageUrl: string | null | undefined;
if (member?.name) {
if (member.getMxcAvatarUrl()) {
imageUrl = mediaFromMxc(member.getMxcAvatarUrl() ?? "").getThumbnailOfSourceHttp(

View file

@ -30,13 +30,14 @@ import DMRoomMap from "../../../utils/DMRoomMap";
import { mediaFromMxc } from "../../../customisations/Media";
import { IOOBData } from "../../../stores/ThreepidInviteStore";
import { LocalRoom } from "../../../models/LocalRoom";
import { filterBoolean } from "../../../utils/arrays";
interface IProps extends Omit<ComponentProps<typeof BaseAvatar>, "name" | "idName" | "url" | "onClick"> {
// Room may be left unset here, but if it is,
// oobData.avatarUrl should be set (else there
// would be nowhere to get the avatar from)
room?: Room;
oobData?: IOOBData & {
oobData: IOOBData & {
roomId?: string;
};
viewAvatarOnClick?: boolean;
@ -86,7 +87,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
};
private static getImageUrls(props: IProps): string[] {
let oobAvatar = null;
let oobAvatar: string | null = null;
if (props.oobData.avatarUrl) {
oobAvatar = mediaFromMxc(props.oobData.avatarUrl).getThumbnailOfSourceHttp(
props.width,
@ -94,28 +95,27 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
props.resizeMethod,
);
}
return [
return filterBoolean([
oobAvatar, // highest priority
RoomAvatar.getRoomAvatarUrl(props),
].filter(function (url) {
return url !== null && url !== "";
});
]);
}
private static getRoomAvatarUrl(props: IProps): string {
private static getRoomAvatarUrl(props: IProps): string | null {
if (!props.room) return null;
return Avatar.avatarUrlForRoom(props.room, props.width, props.height, props.resizeMethod);
}
private onRoomAvatarClick = (): void => {
const avatarUrl = Avatar.avatarUrlForRoom(this.props.room, null, null, null);
const avatarUrl = Avatar.avatarUrlForRoom(this.props.room ?? null, undefined, undefined, undefined);
const params = {
src: avatarUrl,
name: this.props.room.name,
name: this.props.room?.name,
};
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", null, true);
Modal.createDialog(ImageView, params, "mx_Dialog_lightbox", undefined, true);
};
private get roomIdName(): string | undefined {
@ -137,7 +137,7 @@ export default class RoomAvatar extends React.Component<IProps, IState> {
public render(): React.ReactNode {
const { room, oobData, viewAvatarOnClick, onClick, className, ...otherProps } = this.props;
const roomName = room?.name ?? oobData.name;
const roomName = room?.name ?? oobData.name ?? "?";
return (
<BaseAvatar

View file

@ -21,8 +21,10 @@ import { IApp } from "../../../stores/WidgetStore";
import BaseAvatar, { BaseAvatarType } from "./BaseAvatar";
import { mediaFromMxc } from "../../../customisations/Media";
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls"> {
interface IProps extends Omit<ComponentProps<BaseAvatarType>, "name" | "url" | "urls" | "height" | "width"> {
app: IApp;
height?: number;
width?: number;
}
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => {
@ -44,7 +46,7 @@ const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 2
name={app.id}
className={classNames("mx_WidgetAvatar", className)}
// MSC2765
url={app.avatar_url ? mediaFromMxc(app.avatar_url).getSquareThumbnailHttp(20) : undefined}
url={app.avatar_url ? mediaFromMxc(app.avatar_url).getSquareThumbnailHttp(20) : null}
urls={iconUrls}
width={width}
height={height}

View file

@ -31,7 +31,7 @@ function getDisplayUserIdentifier(
// them all as optional. This allows customisers to only define and export the
// customisations they need while still maintaining type safety.
export interface IUserIdentifierCustomisations {
getDisplayUserIdentifier?: typeof getDisplayUserIdentifier;
getDisplayUserIdentifier: typeof getDisplayUserIdentifier;
}
// A real customisation module will define and export one or more of the