Improve strictNullChecks support in right_panel ()

This commit is contained in:
Andy Balaam 2023-03-22 12:15:26 +00:00 committed by GitHub
parent 853b3f822d
commit ba36d2cc01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 63 additions and 52 deletions

View file

@ -79,7 +79,7 @@ interface IProps {
// representing. This may or may not have a room, depending on what it's // 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 // a timeline representing. If it has a room, we maintain RRs etc for
// that room. // that room.
timelineSet: EventTimelineSet; timelineSet?: EventTimelineSet;
// overlay events from a second timelineset on the main timeline // overlay events from a second timelineset on the main timeline
// added to support virtual rooms // added to support virtual rooms
// events from the overlay timeline set will be added by localTimestamp // events from the overlay timeline set will be added by localTimestamp

View file

@ -68,7 +68,7 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const requestFromPromise = await verificationRequestPromise; const requestFromPromise = await verificationRequestPromise;
setRequesting(false); setRequesting(false);
setRequest(requestFromPromise); setRequest(requestFromPromise);
setPhase(requestFromPromise.phase); setPhase(requestFromPromise?.phase);
} }
if (verificationRequestPromise) { if (verificationRequestPromise) {
awaitPromise(); awaitPromise();
@ -109,6 +109,9 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
let verificationRequest_: VerificationRequest; let verificationRequest_: VerificationRequest;
try { try {
const roomId = await ensureDMExists(cli, member.userId); 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); verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
} catch (e) { } catch (e) {
console.error("Error starting verification", e); console.error("Error starting verification", e);
@ -133,15 +136,15 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(null); if (!RightPanelStore.instance.isOpen) RightPanelStore.instance.togglePanel(null);
}, [member]); }, [member]);
const requested = const requested: boolean =
(!request && isRequesting) || (!request && isRequesting) ||
(request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined)); (!!request && (phase === PHASE_REQUESTED || phase === PHASE_UNSENT || phase === undefined));
const isSelfVerification = request const isSelfVerification = request
? request.isSelfVerification ? request.isSelfVerification
: member.userId === MatrixClientPeg.get().getUserId(); : member.userId === MatrixClientPeg.get().getUserId();
if (!request || requested) { if (!request || requested) {
const initiatedByMe = (!request && isRequesting) || (request && request.initiatedByMe); const initiatedByMe = (!request && isRequesting) || (!!request && request.initiatedByMe);
return ( return (
<EncryptionInfo <EncryptionInfo
isRoomEncrypted={isRoomEncrypted} isRoomEncrypted={isRoomEncrypted}

View file

@ -34,7 +34,7 @@ export enum HeaderKind {
interface IState { interface IState {
headerKind: HeaderKind; headerKind: HeaderKind;
phase: RightPanelPhases; phase: RightPanelPhases | null;
threadNotificationColor: NotificationColor; threadNotificationColor: NotificationColor;
globalNotificationColor: NotificationColor; globalNotificationColor: NotificationColor;
} }
@ -43,7 +43,7 @@ interface IProps {}
export default abstract class HeaderButtons<P = {}> extends React.Component<IProps & P, IState> { export default abstract class HeaderButtons<P = {}> extends React.Component<IProps & P, IState> {
private unmounted = false; private unmounted = false;
private dispatcherRef: string; private dispatcherRef?: string = undefined;
public constructor(props: IProps & P, kind: HeaderKind) { public constructor(props: IProps & P, kind: HeaderKind) {
super(props); super(props);
@ -83,7 +83,7 @@ export default abstract class HeaderButtons<P = {}> extends React.Component<IPro
public isPhase(phases: string | string[]): boolean { public isPhase(phases: string | string[]): boolean {
if (!RightPanelStore.instance.isOpen) return false; if (!RightPanelStore.instance.isOpen) return false;
if (Array.isArray(phases)) { if (Array.isArray(phases)) {
return phases.includes(this.state.phase); return !!this.state.phase && phases.includes(this.state.phase);
} else { } else {
return phases === this.state.phase; return phases === this.state.phase;
} }

View file

@ -139,9 +139,10 @@ const PinnedMessagesCard: React.FC<IProps> = ({ room, onClose, permalinkCreator
} }
await room.processPollEvents([event]); await room.processPollEvents([event]);
if (event && PinningUtils.isPinnable(event)) { const senderUserId = event.getSender();
if (senderUserId && PinningUtils.isPinnable(event)) {
// Inject sender information // Inject sender information
event.sender = room.getMember(event.getSender()); event.sender = room.getMember(senderUserId);
// Also inject any edits we've found // Also inject any edits we've found
if (edit) event.makeReplaced(edit); if (edit) event.makeReplaced(edit);

View file

@ -231,7 +231,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
private onRoomSummaryClicked = (): void => { private onRoomSummaryClicked = (): void => {
// use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close // use roomPanelPhase rather than this.state.phase as it remembers the latest one if we close
const currentPhase = RightPanelStore.instance.currentCard.phase; const currentPhase = RightPanelStore.instance.currentCard.phase;
if (ROOM_INFO_PHASES.includes(currentPhase)) { if (currentPhase && ROOM_INFO_PHASES.includes(currentPhase)) {
if (this.state.phase === currentPhase) { if (this.state.phase === currentPhase) {
this.setPhase(currentPhase); this.setPhase(currentPhase);
} else { } else {
@ -257,7 +257,7 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
}; };
private onThreadsPanelClicked = (ev: ButtonEvent): void => { 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); RightPanelStore.instance.togglePanel(this.props.room?.roomId ?? null);
} else { } else {
showThreadPanel(); showThreadPanel();

View file

@ -130,12 +130,14 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>(); const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
let contextMenu; let contextMenu;
if (menuDisplayed) { if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect(); const rect = handle.current?.getBoundingClientRect();
const rightMargin = rect?.right ?? 0;
const topMargin = rect?.top ?? 0;
contextMenu = ( contextMenu = (
<WidgetContextMenu <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
right={UIStore.instance.windowWidth - rect.right} right={UIStore.instance.windowWidth - rightMargin}
bottom={UIStore.instance.windowHeight - rect.top} bottom={UIStore.instance.windowHeight - topMargin}
onFinished={closeMenu} onFinished={closeMenu}
app={app} app={app}
/> />
@ -226,7 +228,7 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
managers.openNoManagerDialog(); managers.openNoManagerDialog();
} else { } else {
// noinspection JSIgnoredPromiseFromCall // noinspection JSIgnoredPromiseFromCall
managers.getPrimaryManager().open(room); managers.getPrimaryManager()?.open(room);
} }
}; };

View file

@ -73,8 +73,8 @@ interface IState {
export default class TimelineCard extends React.Component<IProps, IState> { export default class TimelineCard extends React.Component<IProps, IState> {
public static contextType = RoomContext; public static contextType = RoomContext;
private dispatcherRef: string; private dispatcherRef?: string;
private layoutWatcherRef: string; private layoutWatcherRef?: string;
private timelinePanel = React.createRef<TimelinePanel>(); private timelinePanel = React.createRef<TimelinePanel>();
private card = React.createRef<HTMLDivElement>(); private card = React.createRef<HTMLDivElement>();
private readReceiptsSettingWatcher: string | undefined; private readReceiptsSettingWatcher: string | undefined;
@ -110,10 +110,10 @@ export default class TimelineCard extends React.Component<IProps, IState> {
SettingsStore.unwatchSetting(this.layoutWatcherRef); SettingsStore.unwatchSetting(this.layoutWatcherRef);
} }
dis.unregister(this.dispatcherRef); if (this.dispatcherRef) dis.unregister(this.dispatcherRef);
} }
private onRoomViewStoreUpdate = async (initial?: boolean): Promise<void> => { private onRoomViewStoreUpdate = async (_initial?: boolean): Promise<void> => {
const newState: Pick<IState, any> = { const newState: Pick<IState, any> = {
initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(), initialEventId: SdkContextClass.instance.roomViewStore.getInitialEventId(),
isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(), isInitialEventHighlighted: SdkContextClass.instance.roomViewStore.isInitialEventHighlighted(),
@ -220,7 +220,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
value={{ value={{
...this.context, ...this.context,
timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType, timelineRenderingType: this.props.timelineRenderingType ?? this.context.timelineRenderingType,
liveTimeline: this.props.timelineSet.getLiveTimeline(), liveTimeline: this.props.timelineSet?.getLiveTimeline(),
narrow: this.state.narrow, narrow: this.state.narrow,
}} }}
> >

View file

@ -87,7 +87,7 @@ export interface IDevice extends DeviceInfo {
export const disambiguateDevices = (devices: IDevice[]): void => { export const disambiguateDevices = (devices: IDevice[]): void => {
const names = Object.create(null); const names = Object.create(null);
for (let i = 0; i < devices.length; i++) { for (let i = 0; i < devices.length; i++) {
const name = devices[i].getDisplayName(); const name = devices[i].getDisplayName() ?? "";
const indexList = names[name] || []; const indexList = names[name] || [];
indexList.push(i); indexList.push(i);
names[name] = indexList; names[name] = indexList;
@ -595,7 +595,7 @@ export const RoomKickButton = ({
member, member,
startUpdating, startUpdating,
stopUpdating, stopUpdating,
}: Omit<IBaseRoomProps, "powerLevels">): JSX.Element => { }: Omit<IBaseRoomProps, "powerLevels">): JSX.Element | null => {
const cli = useContext(MatrixClientContext); const cli = useContext(MatrixClientContext);
// check if user can be kicked/disinvited // check if user can be kicked/disinvited
@ -1611,7 +1611,7 @@ const UserInfo: React.FC<IProps> = ({ user, room, onClose, phase = RightPanelPha
const member = useMemo(() => (room ? room.getMember(user.userId) || user : user), [room, user]); const member = useMemo(() => (room ? room.getMember(user.userId) || user : user), [room, user]);
const isRoomEncrypted = useIsEncrypted(cli, room); const isRoomEncrypted = useIsEncrypted(cli, room);
const devices = useDevices(user.userId); const devices = useDevices(user.userId) ?? [];
let e2eStatus: E2EStatus | undefined; let e2eStatus: E2EStatus | undefined;
if (isRoomEncrypted && devices) { if (isRoomEncrypted && devices) {

View file

@ -41,7 +41,7 @@ interface IProps {
layout: string; layout: string;
request: VerificationRequest; request: VerificationRequest;
member: RoomMember | User; member: RoomMember | User;
phase: Phase; phase?: Phase;
onClose: () => void; onClose: () => void;
isRoomEncrypted: boolean; isRoomEncrypted: boolean;
inDialog: boolean; inDialog: boolean;
@ -69,9 +69,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD); const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const brand = SdkConfig.get().brand; const brand = SdkConfig.get().brand;
let noCommonMethodError: JSX.Element | undefined; const noCommonMethodError: JSX.Element | null =
if (!showSAS && !showQR) { !showSAS && !showQR ? (
noCommonMethodError = (
<p> <p>
{_t( {_t(
"The device you are trying to verify doesn't support scanning a " + "The device you are trying to verify doesn't support scanning a " +
@ -80,14 +79,13 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
{ brand }, { brand },
)} )}
</p> </p>
); ) : null;
}
if (this.props.layout === "dialog") { if (this.props.layout === "dialog") {
// HACK: This is a terrible idea. // HACK: This is a terrible idea.
let qrBlockDialog: JSX.Element | undefined; let qrBlockDialog: JSX.Element | undefined;
let sasBlockDialog: JSX.Element | undefined; let sasBlockDialog: JSX.Element | undefined;
if (showQR) { if (showQR && request.qrCodeData) {
qrBlockDialog = ( qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption"> <div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Scan this unique code")}</p> <p>{_t("Scan this unique code")}</p>
@ -135,7 +133,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
} }
let qrBlock: JSX.Element | undefined; let qrBlock: JSX.Element | undefined;
if (showQR) { if (showQR && request.qrCodeData) {
qrBlock = ( qrBlock = (
<div className="mx_UserInfo_container"> <div className="mx_UserInfo_container">
<h3>{_t("Verify by scanning")}</h3> <h3>{_t("Verify by scanning")}</h3>
@ -193,18 +191,23 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private onReciprocateYesClick = (): void => { private onReciprocateYesClick = (): void => {
if (!this.state.reciprocateQREvent) return; if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true }); this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.confirm(); this.state.reciprocateQREvent?.confirm();
}; };
private onReciprocateNoClick = (): void => { private onReciprocateNoClick = (): void => {
if (!this.state.reciprocateQREvent) return; if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true }); this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.cancel(); this.state.reciprocateQREvent?.cancel();
}; };
private getDevice(): DeviceInfo | null { private getDevice(): DeviceInfo | null {
const cli = MatrixClientPeg.get(); const deviceId = this.props.request && this.props.request.channel.deviceId;
return cli.getStoredDevice(cli.getSafeUserId(), 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 { private renderQRReciprocatePhase(): JSX.Element {
@ -398,8 +401,8 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const { request } = this.props; const { request } = this.props;
const sasEvent = (request.verifier as SAS).sasEvent; const sasEvent = (request.verifier as SAS).sasEvent;
const reciprocateQREvent = (request.verifier as ReciprocateQRCode).reciprocateQREvent; const reciprocateQREvent = (request.verifier as ReciprocateQRCode).reciprocateQREvent;
request.verifier.off(SasEvent.ShowSas, this.updateVerifierState); request.verifier?.off(SasEvent.ShowSas, this.updateVerifierState);
request.verifier.off(QrCodeEvent.ShowReciprocateQr, this.updateVerifierState); request.verifier?.off(QrCodeEvent.ShowReciprocateQr, this.updateVerifierState);
this.setState({ sasEvent, reciprocateQREvent }); this.setState({ sasEvent, reciprocateQREvent });
}; };
@ -408,12 +411,12 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const hadVerifier = this.hasVerifier; const hadVerifier = this.hasVerifier;
this.hasVerifier = !!request.verifier; this.hasVerifier = !!request.verifier;
if (!hadVerifier && this.hasVerifier) { if (!hadVerifier && this.hasVerifier) {
request.verifier.on(SasEvent.ShowSas, this.updateVerifierState); request.verifier?.on(SasEvent.ShowSas, this.updateVerifierState);
request.verifier.on(QrCodeEvent.ShowReciprocateQr, this.updateVerifierState); request.verifier?.on(QrCodeEvent.ShowReciprocateQr, this.updateVerifierState);
try { try {
// on the requester side, this is also awaited in startSAS, // on the requester side, this is also awaited in startSAS,
// but that's ok as verify should return the same promise. // but that's ok as verify should return the same promise.
await request.verifier.verify(); await request.verifier?.verify();
} catch (err) { } catch (err) {
logger.error("error verify", err); logger.error("error verify", err);
} }

View file

@ -57,12 +57,14 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
let contextMenu: JSX.Element | undefined; let contextMenu: JSX.Element | undefined;
if (menuDisplayed) { 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 = ( contextMenu = (
<WidgetContextMenu <WidgetContextMenu
chevronFace={ChevronFace.None} chevronFace={ChevronFace.None}
right={UIStore.instance.windowWidth - rect.right - 12} right={UIStore.instance.windowWidth - rightMargin - 12}
top={rect.bottom + 12} top={bottomMargin + 12}
onFinished={closeMenu} onFinished={closeMenu}
app={app} app={app}
/> />

View file

@ -82,7 +82,7 @@ function SendButton(props: ISendButtonProps): JSX.Element {
interface IProps extends MatrixClientProps { interface IProps extends MatrixClientProps {
room: Room; room: Room;
resizeNotifier: ResizeNotifier; resizeNotifier: ResizeNotifier;
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
relation?: IEventRelation; relation?: IEventRelation;
e2eStatus?: E2EStatus; e2eStatus?: E2EStatus;

View file

@ -75,7 +75,7 @@ export function createMessageContent(
model: EditorModel, model: EditorModel,
replyToEvent: MatrixEvent | undefined, replyToEvent: MatrixEvent | undefined,
relation: IEventRelation | undefined, relation: IEventRelation | undefined,
permalinkCreator: RoomPermalinkCreator, permalinkCreator?: RoomPermalinkCreator,
includeReplyLegacyFallback = true, includeReplyLegacyFallback = true,
): IContent { ): IContent {
const isEmote = containsEmote(model); const isEmote = containsEmote(model);
@ -133,7 +133,7 @@ export function isQuickReaction(model: EditorModel): boolean {
interface ISendMessageComposerProps extends MatrixClientProps { interface ISendMessageComposerProps extends MatrixClientProps {
room: Room; room: Room;
placeholder?: string; placeholder?: string;
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
relation?: IEventRelation; relation?: IEventRelation;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
disabled?: boolean; disabled?: boolean;

View file

@ -48,7 +48,7 @@ import { createVoiceMessageContent } from "../../../utils/createVoiceMessageCont
interface IProps { interface IProps {
room: Room; room: Room;
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
relation?: IEventRelation; relation?: IEventRelation;
replyToEvent?: MatrixEvent; replyToEvent?: MatrixEvent;
} }

View file

@ -41,7 +41,7 @@ export enum RightPanelPhases {
ThreadPanel = "ThreadPanel", ThreadPanel = "ThreadPanel",
} }
export function backLabelForPhase(phase: RightPanelPhases): string | null { export function backLabelForPhase(phase: RightPanelPhases | null): string | null {
switch (phase) { switch (phase) {
case RightPanelPhases.ThreadPanel: case RightPanelPhases.ThreadPanel:
return _t("Threads"); return _t("Threads");

View file

@ -68,7 +68,7 @@ export function stripHTMLReply(html: string): string {
// Part of Replies fallback support // Part of Replies fallback support
export function getNestedReplyText( export function getNestedReplyText(
ev: MatrixEvent, ev: MatrixEvent,
permalinkCreator: RoomPermalinkCreator, permalinkCreator?: RoomPermalinkCreator,
): { body: string; html: string } | null { ): { body: string; html: string } | null {
if (!ev) return 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. // 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 userLink = makeUserPermalink(ev.getSender()!);
const mxid = ev.getSender(); const mxid = ev.getSender();
@ -236,7 +236,7 @@ interface AddReplyOpts {
} }
interface IncludeLegacyFeedbackOpts { interface IncludeLegacyFeedbackOpts {
permalinkCreator: RoomPermalinkCreator; permalinkCreator?: RoomPermalinkCreator;
includeLegacyFallback: true; includeLegacyFallback: true;
} }