Apply strictNullChecks to src/components/views/avatars/* (#10254)
This commit is contained in:
parent
eca28ac2f3
commit
ae5725b24c
7 changed files with 41 additions and 31 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue