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( export function avatarUrlForRoom(
room: Room | null, room: Room | null,
width: number, width?: number,
height: number, height?: number,
resizeMethod?: ResizeMethod, resizeMethod?: ResizeMethod,
): string | null { ): string | null {
if (!room) return null; // null-guard if (!room) return null; // null-guard
if (room.getMxcAvatarUrl()) { 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 // 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 // If there are only two members in the DM use the avatar of the other member
const otherMember = room.getAvatarFallbackMember(); const otherMember = room.getAvatarFallbackMember();
if (otherMember?.getMxcAvatarUrl()) { 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; return null;
} }

View file

@ -35,10 +35,10 @@ interface IProps {
name?: string; // The name (first initial used as default) name?: string; // The name (first initial used as default)
idName?: string; // ID for generating hash colours idName?: string; // ID for generating hash colours
title?: string; // onHover title text 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] urls?: string[]; // [highest_priority, ... , lowest_priority]
width?: number; width: number;
height?: number; height: number;
// XXX: resizeMethod not actually used. // XXX: resizeMethod not actually used.
resizeMethod?: ResizeMethod; resizeMethod?: ResizeMethod;
defaultToInitialLetter?: boolean; // true to add default url defaultToInitialLetter?: boolean; // true to add default url
@ -48,7 +48,7 @@ interface IProps {
tabIndex?: number; 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: // work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, ...props.urls ] // imageUrls: [ props.url, ...props.urls ]
@ -66,7 +66,7 @@ const calculateUrls = (url?: string, urls?: string[], lowBandwidth = false): str
return Array.from(new Set(_urls)); 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 // 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 // use the cached lowBandwidth value from the room context if it exists
const roomContext = useContext(RoomContext); const roomContext = useContext(RoomContext);

View file

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

View file

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

View file

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

View file

@ -21,8 +21,10 @@ import { IApp } from "../../../stores/WidgetStore";
import BaseAvatar, { BaseAvatarType } from "./BaseAvatar"; import BaseAvatar, { BaseAvatarType } from "./BaseAvatar";
import { mediaFromMxc } from "../../../customisations/Media"; 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; app: IApp;
height?: number;
width?: number;
} }
const WidgetAvatar: React.FC<IProps> = ({ app, className, width = 20, height = 20, ...props }) => { 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} name={app.id}
className={classNames("mx_WidgetAvatar", className)} className={classNames("mx_WidgetAvatar", className)}
// MSC2765 // 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} urls={iconUrls}
width={width} width={width}
height={height} height={height}

View file

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