From 088080cafd9aa79fd473b717c0b41f5eadc6e70f Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 15 Mar 2023 12:15:23 +0000 Subject: [PATCH] Improve strictNullChecks support in right_panel --- src/components/structures/TimelinePanel.tsx | 2 +- .../views/right_panel/EncryptionPanel.tsx | 11 ++++-- .../views/right_panel/HeaderButtons.tsx | 6 +-- .../views/right_panel/PinnedMessagesCard.tsx | 5 ++- .../views/right_panel/RoomHeaderButtons.tsx | 4 +- .../views/right_panel/RoomSummaryCard.tsx | 10 +++-- .../views/right_panel/TimelineCard.tsx | 10 ++--- src/components/views/right_panel/UserInfo.tsx | 6 +-- .../views/right_panel/VerificationPanel.tsx | 37 ++++++++++--------- .../views/right_panel/WidgetCard.tsx | 8 ++-- .../views/rooms/MessageComposer.tsx | 2 +- .../views/rooms/SendMessageComposer.tsx | 4 +- .../views/rooms/VoiceRecordComposerTile.tsx | 2 +- .../right-panel/RightPanelStorePhases.ts | 2 +- src/utils/Reply.ts | 6 +-- 15 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/components/structures/TimelinePanel.tsx b/src/components/structures/TimelinePanel.tsx index b496454c7ee..719bba71df1 100644 --- a/src/components/structures/TimelinePanel.tsx +++ b/src/components/structures/TimelinePanel.tsx @@ -79,7 +79,7 @@ interface IProps { // representing. This may or may not have a room, depending on what it's // a timeline representing. If it has a room, we maintain RRs etc for // that room. - timelineSet: EventTimelineSet; + timelineSet?: EventTimelineSet; // overlay events from a second timelineset on the main timeline // added to support virtual rooms // events from the overlay timeline set will be added by localTimestamp diff --git a/src/components/views/right_panel/EncryptionPanel.tsx b/src/components/views/right_panel/EncryptionPanel.tsx index b61f3b7c7ec..12e37bba858 100644 --- a/src/components/views/right_panel/EncryptionPanel.tsx +++ b/src/components/views/right_panel/EncryptionPanel.tsx @@ -68,7 +68,7 @@ const EncryptionPanel: React.FC = (props: IProps) => { const requestFromPromise = await verificationRequestPromise; setRequesting(false); setRequest(requestFromPromise); - setPhase(requestFromPromise.phase); + setPhase(requestFromPromise?.phase); } if (verificationRequestPromise) { awaitPromise(); @@ -109,6 +109,9 @@ const EncryptionPanel: React.FC = (props: IProps) => { let verificationRequest_: VerificationRequest; try { const roomId = await ensureDMExists(cli, member.userId); + if (!roomId) { + throw new Error("Unable to create Room for verification"); + } verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId); } catch (e) { console.error("Error starting verification", e); @@ -133,15 +136,15 @@ const EncryptionPanel: React.FC = (props: IProps) => { if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(null); }, [member]); - const requested = + const requested: boolean = (!request && isRequesting) || - (request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined)); + (!!request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined)); const isSelfVerification = request ? request.isSelfVerification : member.userId === MatrixClientPeg.get().getUserId(); if (!request || requested) { - const initiatedByMe = (!request && isRequesting) || (request && request.initiatedByMe); + const initiatedByMe = (!request && isRequesting) || (!!request && request.initiatedByMe); return ( extends React.Component { private unmounted = false; - private dispatcherRef: string; + private dispatcherRef?: string = undefined; public constructor(props: IProps & P, kind: HeaderKind) { super(props); @@ -83,7 +83,7 @@ export default abstract class HeaderButtons

extends React.Component = ({ room, onClose, permalinkCreator } await room.processPollEvents([event]); - if (event && PinningUtils.isPinnable(event)) { + const senderUserId = event.getSender(); + if (senderUserId && PinningUtils.isPinnable(event)) { // Inject sender information - event.sender = room.getMember(event.getSender()); + event.sender = room.getMember(senderUserId); // Also inject any edits we've found if (edit) event.makeReplaced(edit); diff --git a/src/components/views/right_panel/RoomHeaderButtons.tsx b/src/components/views/right_panel/RoomHeaderButtons.tsx index 4d7e873cc07..79bc57515fd 100644 --- a/src/components/views/right_panel/RoomHeaderButtons.tsx +++ b/src/components/views/right_panel/RoomHeaderButtons.tsx @@ -231,7 +231,7 @@ export default class RoomHeaderButtons extends HeaderButtons { private onRoomSummaryClicked = (): void => { // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close const currentPhase = RightPanelStore.instance.currentCard.phase; - if (ROOM_INFO_PHASES.includes(currentPhase)) { + if (currentPhase && ROOM_INFO_PHASES.includes(currentPhase)) { if (this.state.phase === currentPhase) { this.setPhase(currentPhase); } else { @@ -257,7 +257,7 @@ export default class RoomHeaderButtons extends HeaderButtons { }; private onThreadsPanelClicked = (ev: ButtonEvent): void => { - if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { + if (this.state.phase && RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { RightPanelStore.instance.togglePanel(this.props.room?.roomId ?? null); } else { showThreadPanel(); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 5cb8770c96e..0b9b65e7027 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -130,12 +130,14 @@ const AppRow: React.FC = ({ app, room }) => { const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu(); let contextMenu; if (menuDisplayed) { - const rect = handle.current.getBoundingClientRect(); + const rect = handle.current?.getBoundingClientRect(); + const rightMargin = rect?.right ?? 0; + const topMargin = rect?.top ?? 0; contextMenu = ( @@ -226,7 +228,7 @@ const AppsSection: React.FC = ({ room }) => { managers.openNoManagerDialog(); } else { // noinspection JSIgnoredPromiseFromCall - managers.getPrimaryManager().open(room); + managers.getPrimaryManager()?.open(room); } }; diff --git a/src/components/views/right_panel/TimelineCard.tsx b/src/components/views/right_panel/TimelineCard.tsx index 3ccf60d5da5..a236bf4d8b5 100644 --- a/src/components/views/right_panel/TimelineCard.tsx +++ b/src/components/views/right_panel/TimelineCard.tsx @@ -73,8 +73,8 @@ interface IState { export default class TimelineCard extends React.Component { public static contextType = RoomContext; - private dispatcherRef: string; - private layoutWatcherRef: string; + private dispatcherRef?: string; + private layoutWatcherRef?: string; private timelinePanel = React.createRef(); private card = React.createRef(); private readReceiptsSettingWatcher: string | undefined; @@ -110,10 +110,10 @@ export default class TimelineCard extends React.Component { SettingsStore.unwatchSetting(this.layoutWatcherRef); } - dis.unregister(this.dispatcherRef); + if (this.dispatcherRef) dis.unregister(this.dispatcherRef); } - private onRoomViewStoreUpdate = async (initial?: boolean): Promise => { + private onRoomViewStoreUpdate = async (_initial?: boolean): Promise => { const newState: Pick = { initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(), isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(), @@ -220,7 +220,7 @@ export default class TimelineCard extends React.Component { value={{ ...this.context, timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType, - liveTimeline: this.props.timelineSet.getLiveTimeline(), + liveTimeline: this.props.timelineSet?.getLiveTimeline(), narrow: this.state.narrow, }} > diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index d68c1103439..a3dd4cdd392 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -87,7 +87,7 @@ export interface IDevice extends DeviceInfo { export const disambiguateDevices = (devices: IDevice[]): void => { const names = Object.create(null); for (let i = 0; i < devices.length; i++) { - const name = devices[i].getDisplayName(); + const name = devices[i].getDisplayName() ?? ""; const indexList = names[name] || []; indexList.push(i); names[name] = indexList; @@ -595,7 +595,7 @@ export const RoomKickButton = ({ member, startUpdating, stopUpdating, -}: Omit): JSX.Element => { +}: Omit): JSX.Element | null => { const cli = useContext(MatrixClientContext); // check if user can be kicked/disinvited @@ -1611,7 +1611,7 @@ const UserInfo: React.FC = ({ user, room, onClose, phase = RightPanelPha const member = useMemo(() => (room ? room.getMember(user.userId) || user : user), [room, user]); const isRoomEncrypted = useIsEncrypted(cli, room); - const devices = useDevices(user.userId); + const devices = useDevices(user.userId) ?? []; let e2eStatus: E2EStatus | undefined; if (isRoomEncrypted && devices) { diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index da070685c75..1820309e05d 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -41,7 +41,7 @@ interface IProps { layout: string; request: VerificationRequest; member: RoomMember | User; - phase: Phase; + phase?: Phase; onClose: () => void; isRoomEncrypted: boolean; inDialog: boolean; @@ -69,9 +69,8 @@ export default class VerificationPanel extends React.PureComponent {_t( "The device you are trying to verify doesn't support scanning a " + @@ -80,14 +79,13 @@ export default class VerificationPanel extends React.PureComponent - ); - } + ) : null; if (this.props.layout === "dialog") { // HACK: This is a terrible idea. let qrBlockDialog: JSX.Element | undefined; let sasBlockDialog: JSX.Element | undefined; - if (showQR) { + if (showQR && request.qrCodeData) { qrBlockDialog = (

{_t("Scan this unique code")}

@@ -135,7 +133,7 @@ export default class VerificationPanel extends React.PureComponent

{_t("Verify by scanning")}

@@ -193,18 +191,23 @@ export default class VerificationPanel extends React.PureComponent { if (!this.state.reciprocateQREvent) return; this.setState({ reciprocateButtonClicked: true }); - this.state.reciprocateQREvent.confirm(); + this.state.reciprocateQREvent?.confirm(); }; private onReciprocateNoClick = (): void => { if (!this.state.reciprocateQREvent) return; this.setState({ reciprocateButtonClicked: true }); - this.state.reciprocateQREvent.cancel(); + this.state.reciprocateQREvent?.cancel(); }; private getDevice(): DeviceInfo | null { - const cli = MatrixClientPeg.get(); - return cli.getStoredDevice(cli.getSafeUserId(), this.props.request?.channel.deviceId); + const deviceId = this.props.request && this.props.request.channel.deviceId; + const userId = MatrixClientPeg.get().getUserId(); + if (deviceId && userId) { + return MatrixClientPeg.get().getStoredDevice(userId, deviceId); + } else { + return null; + } } private renderQRReciprocatePhase(): JSX.Element { @@ -398,8 +401,8 @@ export default class VerificationPanel extends React.PureComponent = ({ room, widgetId, onClose }) => { let contextMenu: JSX.Element | undefined; if (menuDisplayed) { - const rect = handle.current!.getBoundingClientRect(); + const rect = handle.current?.getBoundingClientRect(); + const rightMargin = rect ? rect.right : 0; + const bottomMargin = rect ? rect.bottom : 0; contextMenu = ( diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 8fc647b77a3..2e4be003c41 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -82,7 +82,7 @@ function SendButton(props: ISendButtonProps): JSX.Element { interface IProps extends MatrixClientProps { room: Room; resizeNotifier: ResizeNotifier; - permalinkCreator: RoomPermalinkCreator; + permalinkCreator?: RoomPermalinkCreator; replyToEvent?: MatrixEvent; relation?: IEventRelation; e2eStatus?: E2EStatus; diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index d3d7e3847d6..d10db80df4d 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -75,7 +75,7 @@ export function createMessageContent( model: EditorModel, replyToEvent: MatrixEvent | undefined, relation: IEventRelation | undefined, - permalinkCreator: RoomPermalinkCreator, + permalinkCreator?: RoomPermalinkCreator, includeReplyLegacyFallback = true, ): IContent { const isEmote = containsEmote(model); @@ -133,7 +133,7 @@ export function isQuickReaction(model: EditorModel): boolean { interface ISendMessageComposerProps extends MatrixClientProps { room: Room; placeholder?: string; - permalinkCreator: RoomPermalinkCreator; + permalinkCreator?: RoomPermalinkCreator; relation?: IEventRelation; replyToEvent?: MatrixEvent; disabled?: boolean; diff --git a/src/components/views/rooms/VoiceRecordComposerTile.tsx b/src/components/views/rooms/VoiceRecordComposerTile.tsx index 2f7be4c9b29..8a057eb2061 100644 --- a/src/components/views/rooms/VoiceRecordComposerTile.tsx +++ b/src/components/views/rooms/VoiceRecordComposerTile.tsx @@ -48,7 +48,7 @@ import { createVoiceMessageContent } from "../../../utils/createVoiceMessageCont interface IProps { room: Room; - permalinkCreator: RoomPermalinkCreator; + permalinkCreator?: RoomPermalinkCreator; relation?: IEventRelation; replyToEvent?: MatrixEvent; } diff --git a/src/stores/right-panel/RightPanelStorePhases.ts b/src/stores/right-panel/RightPanelStorePhases.ts index 326651e3c14..9c96953e11d 100644 --- a/src/stores/right-panel/RightPanelStorePhases.ts +++ b/src/stores/right-panel/RightPanelStorePhases.ts @@ -41,7 +41,7 @@ export enum RightPanelPhases { ThreadPanel = "ThreadPanel", } -export function backLabelForPhase(phase: RightPanelPhases): string | null { +export function backLabelForPhase(phase: RightPanelPhases | null): string | null { switch (phase) { case RightPanelPhases.ThreadPanel: return _t("Threads"); diff --git a/src/utils/Reply.ts b/src/utils/Reply.ts index dc3e8da2a34..51afff92f6b 100644 --- a/src/utils/Reply.ts +++ b/src/utils/Reply.ts @@ -68,7 +68,7 @@ export function stripHTMLReply(html: string): string { // Part of Replies fallback support export function getNestedReplyText( ev: MatrixEvent, - permalinkCreator: RoomPermalinkCreator, + permalinkCreator?: RoomPermalinkCreator, ): { body: string; html: string } | null { if (!ev) return null; @@ -99,7 +99,7 @@ export function getNestedReplyText( // dev note: do not rely on `body` being safe for HTML usage below. - const evLink = permalinkCreator.forEvent(ev.getId()!); + const evLink = permalinkCreator?.forEvent(ev.getId()!); const userLink = makeUserPermalink(ev.getSender()!); const mxid = ev.getSender(); @@ -236,7 +236,7 @@ interface AddReplyOpts { } interface IncludeLegacyFeedbackOpts { - permalinkCreator: RoomPermalinkCreator; + permalinkCreator?: RoomPermalinkCreator; includeLegacyFallback: true; }