Conform more code to strictNullChecks (#10374)

* Apply `strictNullChecks` to `src/components/views/room_settings/*`

* Restore tsconfig.json

* Conform more code to `strictNullChecks`

* Iterate

* Update matrix-widget-api

* Conform more code to `strictNullChecks`
This commit is contained in:
Michael Telatynski 2023-03-16 11:07:29 +00:00 committed by GitHub
parent 9c816bb720
commit 1c9ea423c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 223 additions and 179 deletions

View file

@ -25,7 +25,7 @@ import { IS_MAC } from "./Keyboard";
* The combo is evaluated strictly, i.e. the KeyboardEvent must match exactly what is specified in the KeyCombo.
*/
export type KeyCombo = {
key?: string;
key: string;
/** On PC: ctrl is pressed; on Mac: meta is pressed */
ctrlOrCmdKey?: boolean;

View file

@ -306,7 +306,7 @@ class MatrixClientPegClass implements IMatrixClientPeg {
homeserverUrl: this.matrixClient.baseUrl,
identityServerUrl: this.matrixClient.idBaseUrl,
userId: this.matrixClient.credentials.userId,
deviceId: this.matrixClient.getDeviceId(),
deviceId: this.matrixClient.getDeviceId() ?? undefined,
accessToken: this.matrixClient.getAccessToken(),
guest: this.matrixClient.isGuest(),
};

View file

@ -576,7 +576,7 @@ async function combinedPagination(searchResult: ISeshatSearchResults): Promise<I
const newSlice = result.results.slice(Math.max(result.results.length - newResultCount, 0));
restoreEncryptionInfo(newSlice);
searchResult.pendingRequest = null;
searchResult.pendingRequest = undefined;
return result;
}

View file

@ -41,7 +41,8 @@ interface IProps {
defaultName?: string;
parentSpace?: Room;
defaultEncrypted?: boolean;
onFinished(proceed?: boolean, opts?: IOpts): void;
onFinished(proceed?: false): void;
onFinished(proceed: true, opts: IOpts): void;
}
interface IState {

View file

@ -46,7 +46,8 @@ interface IProps {
widgetDefinition: IModalWidgetOpenRequestData;
widgetRoomId?: string;
sourceWidgetId: string;
onFinished(success?: boolean, data?: IModalWidgetReturnData): void;
onFinished(success: true, data: IModalWidgetReturnData): void;
onFinished(success?: false, data?: void): void;
}
interface IState {

View file

@ -133,13 +133,13 @@ export default class ImageView extends React.Component<IProps, IState> {
// We want to recalculate zoom whenever the window's size changes
window.addEventListener("resize", this.recalculateZoom);
// After the image loads for the first time we want to calculate the zoom
this.image.current.addEventListener("load", this.imageLoaded);
this.image.current?.addEventListener("load", this.imageLoaded);
}
public componentWillUnmount(): void {
this.focusLock.current.removeEventListener("wheel", this.onWheel);
window.removeEventListener("resize", this.recalculateZoom);
this.image.current.removeEventListener("load", this.imageLoaded);
this.image.current?.removeEventListener("load", this.imageLoaded);
}
private imageLoaded = (): void => {
@ -171,6 +171,7 @@ export default class ImageView extends React.Component<IProps, IState> {
private setZoomAndRotation = (inputRotation?: number): void => {
const image = this.image.current;
const imageWrapper = this.imageWrapper.current;
if (!image || !imageWrapper) return;
const rotation = inputRotation ?? this.state.rotation;
@ -236,8 +237,8 @@ export default class ImageView extends React.Component<IProps, IState> {
// Zoom relative to the given point on the image.
// First we need to figure out the offset of the anchor point
// relative to the center of the image, accounting for rotation.
let offsetX;
let offsetY;
let offsetX: number | undefined;
let offsetY: number | undefined;
// The modulo operator can return negative values for some
// rotations, so we have to do some extra work to normalize it
switch (((this.state.rotation % 360) + 360) % 360) {
@ -310,7 +311,7 @@ export default class ImageView extends React.Component<IProps, IState> {
private onDownloadClick = (): void => {
const a = document.createElement("a");
a.href = this.props.src;
a.download = this.props.name;
if (this.props.name) a.download = this.props.name;
a.target = "_blank";
a.rel = "noreferrer noopener";
a.click();
@ -334,9 +335,9 @@ export default class ImageView extends React.Component<IProps, IState> {
ev.preventDefault();
dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
event_id: this.props.mxEvent.getId(),
event_id: this.props.mxEvent?.getId(),
highlighted: true,
room_id: this.props.mxEvent.getRoomId(),
room_id: this.props.mxEvent?.getRoomId(),
metricsTrigger: undefined, // room doesn't change
});
this.props.onFinished();
@ -440,11 +441,11 @@ export default class ImageView extends React.Component<IProps, IState> {
let info: JSX.Element | undefined;
if (showEventMeta) {
const mxEvent = this.props.mxEvent;
const mxEvent = this.props.mxEvent!;
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");
let permalink = "#";
if (this.props.permalinkCreator) {
permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
permalink = this.props.permalinkCreator.forEvent(mxEvent.getId());
}
const senderName = mxEvent.sender?.name ?? mxEvent.getSender();
@ -453,7 +454,7 @@ export default class ImageView extends React.Component<IProps, IState> {
<a
href={permalink}
onClick={this.onPermalinkClicked}
aria-label={formatFullDate(new Date(this.props.mxEvent.getTs()), showTwelveHour, false)}
aria-label={formatFullDate(new Date(mxEvent.getTs()), showTwelveHour, false)}
>
<MessageTimestamp
showFullDate={true}

View file

@ -66,17 +66,17 @@ interface IProps<T> {
// should typically be less than `overflowItems` unless applying
// margins in the parent component when using multiple LazyRenderList in one viewport.
// use 0 to only rerender when items will come into view.
overflowMargin?: number;
overflowMargin: number;
// the amount of items to add at the top and bottom to render,
// so not every scroll of causes a rerender.
overflowItems?: number;
overflowItems: number;
element?: string;
className?: string;
}
interface IState {
renderRange: ItemRange | null;
renderRange: ItemRange;
}
export default class LazyRenderList<T = any> extends React.Component<IProps<T>, IState> {
@ -88,9 +88,7 @@ export default class LazyRenderList<T = any> extends React.Component<IProps<T>,
public constructor(props: IProps<T>) {
super(props);
this.state = {
renderRange: null,
};
this.state = LazyRenderList.getDerivedStateFromProps(props, {} as IState) as IState;
}
public static getDerivedStateFromProps(props: IProps<unknown>, state: IState): Partial<IState> | null {

View file

@ -35,7 +35,7 @@ const ALGORITHM = "m.megolm.v1.aes-sha2";
const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp }, ref) => {
const cli = useContext(MatrixClientContext);
const roomId = mxEvent.getRoomId();
const roomId = mxEvent.getRoomId()!;
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);
const prevContent = mxEvent.getPrevContent() as IRoomEncryption;
@ -51,13 +51,13 @@ const EncryptionEvent = forwardRef<HTMLDivElement, IProps>(({ mxEvent, timestamp
if (prevContent.algorithm === ALGORITHM) {
subtitle = _t("Some encryption parameters have been changed.");
} else if (dmPartner) {
const displayName = room.getMember(dmPartner)?.rawDisplayName || dmPartner;
const displayName = room?.getMember(dmPartner)?.rawDisplayName || dmPartner;
subtitle = _t(
"Messages here are end-to-end encrypted. " +
"Verify %(displayName)s in their profile - tap on their avatar.",
{ displayName },
);
} else if (isLocalRoom(room)) {
} else if (room && isLocalRoom(room)) {
subtitle = _t("Messages in this chat will be end-to-end encrypted.");
} else {
subtitle = _t(

View file

@ -183,7 +183,7 @@ const MBeaconBody: React.FC<IBodyProps> = React.forwardRef(({ mxEvent, getRelati
<SmartMarker
map={map}
id={`${mapId}-marker`}
geoUri={latestLocationState.uri}
geoUri={latestLocationState?.uri}
roomMember={markerRoomMember ?? undefined}
useMemberColor
/>

View file

@ -47,10 +47,10 @@ interface IProps {
room: Room;
onClose: () => void;
resizeNotifier: ResizeNotifier;
permalinkCreator?: RoomPermalinkCreator;
permalinkCreator: RoomPermalinkCreator;
e2eStatus?: E2EStatus;
classNames?: string;
timelineSet?: EventTimelineSet;
timelineSet: EventTimelineSet;
timelineRenderingType?: TimelineRenderingType;
showComposer?: boolean;
composerRelation?: IEventRelation;
@ -77,7 +77,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
private layoutWatcherRef: string;
private timelinePanel = React.createRef<TimelinePanel>();
private card = React.createRef<HTMLDivElement>();
private readReceiptsSettingWatcher: string;
private readReceiptsSettingWatcher: string | undefined;
public constructor(props: IProps) {
super(props);
@ -87,7 +87,6 @@ export default class TimelineCard extends React.Component<IProps, IState> {
atEndOfLiveTimeline: true,
narrow: false,
};
this.readReceiptsSettingWatcher = null;
}
public componentDidMount(): void {
@ -129,7 +128,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
case Action.EditEvent:
this.setState(
{
editState: payload.event ? new EditorStateTransfer(payload.event) : null,
editState: payload.event ? new EditorStateTransfer(payload.event) : undefined,
},
() => {
if (payload.event) {
@ -199,7 +198,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
};
public render(): React.ReactNode {
const highlightedEventId = this.state.isInitialEventHighlighted ? this.state.initialEventId : null;
const highlightedEventId = this.state.isInitialEventHighlighted ? this.state.initialEventId : undefined;
let jumpToBottom;
if (!this.state.atEndOfLiveTimeline) {
@ -232,7 +231,7 @@ export default class TimelineCard extends React.Component<IProps, IState> {
header={this.renderTimelineCardHeader()}
ref={this.card}
>
<Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />
{this.card.current && <Measured sensor={this.card.current} onMeasurement={this.onMeasurement} />}
<div className="mx_TimelineCard_timeline">
{jumpToBottom}
<TimelinePanel

View file

@ -69,8 +69,9 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const showQR: boolean = request.otherPartySupportsMethod(SCAN_QR_CODE_METHOD);
const brand = SdkConfig.get().brand;
const noCommonMethodError: JSX.Element =
!showSAS && !showQR ? (
let noCommonMethodError: JSX.Element | undefined;
if (!showSAS && !showQR) {
noCommonMethodError = (
<p>
{_t(
"The device you are trying to verify doesn't support scanning a " +
@ -79,12 +80,13 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
{ brand },
)}
</p>
) : null;
);
}
if (this.props.layout === "dialog") {
// HACK: This is a terrible idea.
let qrBlockDialog: JSX.Element;
let sasBlockDialog: JSX.Element;
let qrBlockDialog: JSX.Element | undefined;
let sasBlockDialog: JSX.Element | undefined;
if (showQR) {
qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption">
@ -132,7 +134,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
);
}
let qrBlock: JSX.Element;
let qrBlock: JSX.Element | undefined;
if (showQR) {
qrBlock = (
<div className="mx_UserInfo_container">
@ -150,7 +152,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
);
}
let sasBlock: JSX.Element;
let sasBlock: JSX.Element | undefined;
if (showSAS) {
const disabled = this.state.emojiButtonClicked;
const sasLabel = showQR
@ -189,18 +191,20 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
}
private onReciprocateYesClick = (): void => {
if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.confirm();
};
private onReciprocateNoClick = (): void => {
if (!this.state.reciprocateQREvent) return;
this.setState({ reciprocateButtonClicked: true });
this.state.reciprocateQREvent.cancel();
};
private getDevice(): DeviceInfo {
const deviceId = this.props.request && this.props.request.channel.deviceId;
return MatrixClientPeg.get().getStoredDevice(MatrixClientPeg.get().getUserId(), deviceId);
private getDevice(): DeviceInfo | null {
const cli = MatrixClientPeg.get();
return cli.getStoredDevice(cli.getSafeUserId(), this.props.request?.channel.deviceId);
}
private renderQRReciprocatePhase(): JSX.Element {
@ -253,7 +257,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
private renderVerifiedPhase(): JSX.Element {
const { member, request } = this.props;
let text: string;
let text: string | undefined;
if (!request.isSelfVerification) {
if (this.props.isRoomEncrypted) {
text = _t("Verify all users in a room to ensure it's secure.");
@ -348,7 +352,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const emojis = this.state.sasEvent ? (
<VerificationShowSas
displayName={displayName}
device={this.getDevice()}
device={this.getDevice() ?? undefined}
sas={this.state.sasEvent.sas}
onCancel={this.onSasMismatchesClick}
onDone={this.onSasMatchesClick}
@ -383,11 +387,11 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
};
private onSasMatchesClick = (): void => {
this.state.sasEvent.confirm();
this.state.sasEvent?.confirm();
};
private onSasMismatchesClick = (): void => {
this.state.sasEvent.mismatch();
this.state.sasEvent?.mismatch();
};
private updateVerifierState = (): void => {

View file

@ -55,9 +55,9 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
// Don't render anything as we are about to transition
if (!app || !isRight) return null;
let contextMenu;
let contextMenu: JSX.Element | undefined;
if (menuDisplayed) {
const rect = handle.current.getBoundingClientRect();
const rect = handle.current!.getBoundingClientRect();
contextMenu = (
<WidgetContextMenu
chevronFace={ChevronFace.None}
@ -92,7 +92,7 @@ const WidgetCard: React.FC<IProps> = ({ room, widgetId, onClose }) => {
fullWidth
showMenubar={false}
room={room}
userId={cli.getUserId()}
userId={cli.getSafeUserId()}
creatorUserId={app.creatorUserId}
widgetPageTitle={WidgetUtils.getWidgetDataTitle(app)}
waitForIframeLoad={app.waitForIframeLoad}

View file

@ -252,10 +252,10 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
}
if (isEmpty) {
this.formatBarRef.current.hide();
this.formatBarRef.current?.hide();
}
this.setState({
autoComplete: this.props.model.autoComplete,
autoComplete: this.props.model.autoComplete ?? undefined,
// if a change is happening then clear the showVisualBell
showVisualBell: diff ? false : this.state.showVisualBell,
});
@ -266,12 +266,16 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
// If the user is entering a command, only consider them typing if it is one which sends a message into the room
if (isTyping && this.props.model.parts[0].type === "command") {
const { cmd } = parseCommandString(this.props.model.parts[0].text);
const command = CommandMap.get(cmd);
if (!command || !command.isEnabled() || command.category !== CommandCategories.messages) {
const command = CommandMap.get(cmd!);
if (!command?.isEnabled() || command.category !== CommandCategories.messages) {
isTyping = false;
}
}
SdkContextClass.instance.typingStore.setSelfTyping(this.props.room.roomId, this.props.threadId, isTyping);
SdkContextClass.instance.typingStore.setSelfTyping(
this.props.room.roomId,
this.props.threadId ?? null,
isTyping,
);
if (this.props.onChange) {
this.props.onChange();
@ -280,14 +284,14 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private showPlaceholder(): void {
// escape single quotes
const placeholder = this.props.placeholder.replace(/'/g, "\\'");
this.editorRef.current.style.setProperty("--placeholder", `'${placeholder}'`);
this.editorRef.current.classList.add("mx_BasicMessageComposer_inputEmpty");
const placeholder = this.props.placeholder?.replace(/'/g, "\\'");
this.editorRef.current?.style.setProperty("--placeholder", `'${placeholder}'`);
this.editorRef.current?.classList.add("mx_BasicMessageComposer_inputEmpty");
}
private hidePlaceholder(): void {
this.editorRef.current.classList.remove("mx_BasicMessageComposer_inputEmpty");
this.editorRef.current.style.removeProperty("--placeholder");
this.editorRef.current?.classList.remove("mx_BasicMessageComposer_inputEmpty");
this.editorRef.current?.style.removeProperty("--placeholder");
}
private onCompositionStart = (): void => {
@ -327,9 +331,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
private onCutCopy = (event: ClipboardEvent, type: string): void => {
const selection = document.getSelection();
const selection = document.getSelection()!;
const text = selection.toString();
if (text) {
if (text && this.editorRef.current) {
const { model } = this.props;
const range = getRangeForSelection(this.editorRef.current, model, selection);
const selectedParts = range.parts.map((p) => p.serialize());
@ -412,7 +416,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const { model } = this.props;
this._isCaretAtEnd = position.isAtEnd(model);
this.lastCaret = position.asOffset(model);
this.lastSelection = cloneSelection(document.getSelection());
this.lastSelection = cloneSelection(document.getSelection()!);
}
private refreshLastCaretIfNeeded(): DocumentOffset | undefined {
@ -422,7 +426,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
if (!this.editorRef.current) {
return;
}
const selection = document.getSelection();
const selection = document.getSelection()!;
if (!this.lastSelection || !selectionEquals(this.lastSelection, selection)) {
this.lastSelection = cloneSelection(selection);
const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection);
@ -467,7 +471,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const { isEmpty } = this.props.model;
this.refreshLastCaretIfNeeded();
const selection = document.getSelection();
const selection = document.getSelection()!;
if (this.hasTextSelected && selection.isCollapsed) {
this.hasTextSelected = false;
this.formatBarRef.current?.hide();
@ -485,7 +489,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
const model = this.props.model;
let handled = false;
if (this.state.surroundWith && document.getSelection().type !== "Caret") {
if (this.state.surroundWith && document.getSelection()!.type !== "Caret") {
// This surrounds the selected text with a character. This is
// intentionally left out of the keybinding manager as the keybinds
// here shouldn't be changeable
@ -538,7 +542,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.tabCompleteName();
handled = true;
} else if ([KeyBindingAction.Delete, KeyBindingAction.Backspace].includes(accessibilityAction!)) {
this.formatBarRef.current.hide();
this.formatBarRef.current?.hide();
}
if (handled) {
@ -654,7 +658,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private onAutoCompleteConfirm = (completion: ICompletion): void => {
this.modifiedFlag = true;
this.props.model.autoComplete.onComponentConfirm(completion);
this.props.model.autoComplete?.onComponentConfirm(completion);
};
private onAutoCompleteSelectionChange = (completionIndex: number): void => {
@ -691,9 +695,9 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
public componentWillUnmount(): void {
document.removeEventListener("selectionchange", this.onSelectionChange);
this.editorRef.current.removeEventListener("input", this.onInput, true);
this.editorRef.current.removeEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current.removeEventListener("compositionend", this.onCompositionEnd, true);
this.editorRef.current?.removeEventListener("input", this.onInput, true);
this.editorRef.current?.removeEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current?.removeEventListener("compositionend", this.onCompositionEnd, true);
SettingsStore.unwatchSetting(this.useMarkdownHandle);
SettingsStore.unwatchSetting(this.emoticonSettingHandle);
SettingsStore.unwatchSetting(this.shouldShowPillAvatarSettingHandle);
@ -716,10 +720,10 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
this.updateEditorState(this.getInitialCaretPosition());
// attach input listener by hand so React doesn't proxy the events,
// as the proxied event doesn't support inputType, which we need.
this.editorRef.current.addEventListener("input", this.onInput, true);
this.editorRef.current.addEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current.addEventListener("compositionend", this.onCompositionEnd, true);
this.editorRef.current.focus();
this.editorRef.current?.addEventListener("input", this.onInput, true);
this.editorRef.current?.addEventListener("compositionstart", this.onCompositionStart, true);
this.editorRef.current?.addEventListener("compositionend", this.onCompositionEnd, true);
this.editorRef.current?.focus();
}
private getInitialCaretPosition(): DocumentPosition {
@ -826,7 +830,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
public focus(): void {
this.editorRef.current.focus();
this.editorRef.current?.focus();
}
public insertMention(userId: string): void {

View file

@ -149,7 +149,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
this.dispatcherRef = dis.register(this.onAction);
}
private getRoom(): Room {
private getRoom(): Room | null {
return this.props.mxClient.getRoom(this.props.editState.getEvent().getRoomId());
}
@ -237,10 +237,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
}
private get events(): MatrixEvent[] {
const liveTimelineEvents = this.context.liveTimeline.getEvents();
const pendingEvents = this.getRoom().getPendingEvents();
const liveTimelineEvents = this.context.liveTimeline?.getEvents();
const pendingEvents = this.getRoom()?.getPendingEvents();
const isInThread = Boolean(this.props.editState.getEvent().getThread());
return liveTimelineEvents.concat(isInThread ? [] : pendingEvents);
return liveTimelineEvents?.concat(isInThread ? [] : pendingEvents) ?? [];
}
private cancelEdit = (): void => {
@ -304,10 +304,10 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
});
// Replace emoticon at the end of the message
if (SettingsStore.getValue("MessageComposerInput.autoReplaceEmoji")) {
const caret = this.editorRef.current?.getCaret();
if (SettingsStore.getValue("MessageComposerInput.autoReplaceEmoji") && this.editorRef.current) {
const caret = this.editorRef.current.getCaret();
const position = this.model.positionForOffset(caret.offset, caret.atNodeEnd);
this.editorRef.current?.replaceEmoticon(position, REGEX_EMOTICON);
this.editorRef.current.replaceEmoticon(position, REGEX_EMOTICON);
}
const editContent = createEditContent(this.model, editedEvent);
const newContent = editContent["m.new_content"];
@ -382,7 +382,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
// store caret and serialized parts in the
// editorstate so it can be restored when the remote echo event tile gets rendered
// in case we're currently editing a pending event
const sel = document.getSelection();
const sel = document.getSelection()!;
let caret: DocumentOffset | undefined;
if (sel.focusNode) {
caret = getCaretOffsetAndText(this.editorRef.current?.editorRef.current, sel).caret;

View file

@ -238,7 +238,7 @@ interface IState {
isQuoteExpanded?: boolean;
thread: Thread;
thread: Thread | null;
threadNotification?: NotificationCountType;
}
@ -438,12 +438,12 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
if (thread.id === this.props.mxEvent.getId()) {
this.updateThread(thread);
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
room.off(ThreadEvent.New, this.onNewThread);
room?.off(ThreadEvent.New, this.onNewThread);
}
};
private get thread(): Thread | null {
let thread = this.props.mxEvent.getThread();
let thread: Thread | undefined = this.props.mxEvent.getThread();
/**
* Accessing the threads value through the room due to a race condition
* that will be solved when there are proper backend support for threads
@ -452,7 +452,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
*/
if (!thread) {
const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
thread = room?.findThreadForEvent(this.props.mxEvent);
thread = room?.findThreadForEvent(this.props.mxEvent) ?? undefined;
}
return thread ?? null;
}
@ -471,7 +471,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
}
private renderThreadInfo(): React.ReactNode {
if (this.state.thread?.id === this.props.mxEvent.getId()) {
if (this.state.thread && this.state.thread.id === this.props.mxEvent.getId()) {
return (
<ThreadSummary mxEvent={this.props.mxEvent} thread={this.state.thread} data-testid="thread-summary" />
);
@ -506,7 +506,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
evt.preventDefault();
evt.stopPropagation();
const { permalinkCreator, mxEvent } = this.props;
const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId());
const matrixToUrl = permalinkCreator.forEvent(mxEvent.getId()!);
await copyPlaintext(matrixToUrl);
};
@ -1104,7 +1104,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
const useIRCLayout = this.props.layout === Layout.IRC;
const groupTimestamp = !useIRCLayout ? linkedTimestamp : null;
const ircTimestamp = useIRCLayout ? linkedTimestamp : null;
const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : null;
const bubbleTimestamp = this.props.layout === Layout.Bubble ? messageTimestamp : undefined;
const groupPadlock = !useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
const ircPadlock = useIRCLayout && !isBubbleMessage && this.renderE2EPadlock();
@ -1493,7 +1493,7 @@ class E2ePadlock extends React.Component<IE2ePadlockProps, IE2ePadlockState> {
}
interface ISentReceiptProps {
messageState: string; // TODO: Types for message sending state
messageState: EventStatus | null;
}
function SentReceipt({ messageState }: ISentReceiptProps): JSX.Element {

View file

@ -164,7 +164,7 @@ const NewRoomIntro: React.FC = () => {
}
const creator = room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
const creatorName = room?.getMember(creator)?.rawDisplayName || creator;
const creatorName = (creator && room?.getMember(creator)?.rawDisplayName) || creator;
let createdText: string;
if (creator === cli.getUserId()) {
@ -178,7 +178,7 @@ const NewRoomIntro: React.FC = () => {
let parentSpace: Room | undefined;
if (
SpaceStore.instance.activeSpaceRoom?.canInvite(cli.getSafeUserId()) &&
SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace, room.roomId)
SpaceStore.instance.isRoomInSpace(SpaceStore.instance.activeSpace!, room.roomId)
) {
parentSpace = SpaceStore.instance.activeSpaceRoom;
}

View file

@ -231,7 +231,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
contextMenu = (
<IconizedContextMenu
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())}
{...contextMenuBelow(plusMenuHandle.current!.getBoundingClientRect())}
onFinished={closePlusMenu}
compact
>
@ -357,7 +357,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
contextMenu = (
<IconizedContextMenu
{...contextMenuBelow(plusMenuHandle.current.getBoundingClientRect())}
{...contextMenuBelow(plusMenuHandle.current!.getBoundingClientRect())}
onFinished={closePlusMenu}
compact
>

View file

@ -227,12 +227,14 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// selection must be collapsed and caret at start
if (this.editorRef.current?.isSelectionCollapsed() && this.editorRef.current?.isCaretAtStart()) {
const events = this.context.liveTimeline
.getEvents()
?.getEvents()
.concat(replyingToThread ? [] : this.props.room.getPendingEvents());
const editEvent = findEditableEvent({
const editEvent = events
? findEditableEvent({
events,
isForward: false,
});
})
: undefined;
if (editEvent) {
// We're selecting history, so prevent the key event from doing anything else
event.preventDefault();
@ -297,7 +299,7 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
if (events[i].getType() === EventType.RoomMessage) {
let shouldReact = true;
const lastMessage = events[i];
const userId = MatrixClientPeg.get().getUserId();
const userId = MatrixClientPeg.get().getSafeUserId();
const messageReactions = this.props.room.relations.getChildEventsForEvent(
lastMessage.getId()!,
RelationType.Annotation,
@ -307,10 +309,10 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
// if we have already sent this reaction, don't redact but don't re-send
if (messageReactions) {
const myReactionEvents =
messageReactions.getAnnotationsBySender()[userId] || new Set<MatrixEvent>();
messageReactions.getAnnotationsBySender()?.[userId] || new Set<MatrixEvent>();
const myReactionKeys = [...myReactionEvents]
.filter((event) => !event.isRedacted())
.map((event) => event.getRelation().key);
.map((event) => event.getRelation()?.key);
shouldReact = !myReactionKeys.includes(reaction);
}
if (shouldReact) {

View file

@ -46,7 +46,7 @@ interface IKeyboardShortcutProps {
export const KeyboardShortcut: React.FC<IKeyboardShortcutProps> = ({ value, className = "mx_KeyboardShortcut" }) => {
if (!value) return null;
const modifiersElement = [];
const modifiersElement: JSX.Element[] = [];
if (value.ctrlOrCmdKey) {
modifiersElement.push(<KeyboardKey key="ctrlOrCmdKey" name={IS_MAC ? Key.META : Key.CONTROL} />);
} else if (value.ctrlKey) {

View file

@ -41,7 +41,7 @@ import SdkConfig from "../../../SdkConfig";
import AccessibleButton from "../elements/AccessibleButton";
import TagComposer from "../elements/TagComposer";
import { objectClone } from "../../../utils/objects";
import { arrayDiff } from "../../../utils/arrays";
import { arrayDiff, filterBoolean } from "../../../utils/arrays";
import { clearAllNotifications, getLocalNotificationAccountDataEventType } from "../../../utils/notifications";
import {
updateExistingPushRulesWithActions,
@ -167,7 +167,7 @@ const maximumVectorState = (
if (!definition.syncedRuleIds?.length) {
return undefined;
}
const vectorState = definition.syncedRuleIds.reduce<VectorState>((maxVectorState, ruleId) => {
const vectorState = definition.syncedRuleIds.reduce<VectorState | undefined>((maxVectorState, ruleId) => {
// already set to maximum
if (maxVectorState === VectorState.Loud) {
return maxVectorState;
@ -345,7 +345,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
for (const rule of defaultRules[category]) {
const definition: VectorPushRuleDefinition = VectorPushRulesDefinitions[rule.rule_id];
const vectorState = definition.ruleToVectorState(rule);
preparedNewState.vectorPushRules[category].push({
preparedNewState.vectorPushRules[category]!.push({
ruleId: rule.rule_id,
rule,
vectorState,
@ -355,7 +355,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}
// Quickly sort the rules for display purposes
preparedNewState.vectorPushRules[category].sort((a, b) => {
preparedNewState.vectorPushRules[category]!.sort((a, b) => {
let idxA = RULE_DISPLAY_ORDER.indexOf(a.ruleId);
let idxB = RULE_DISPLAY_ORDER.indexOf(b.ruleId);
@ -367,7 +367,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
});
if (category === KEYWORD_RULE_CATEGORY) {
preparedNewState.vectorPushRules[category].push({
preparedNewState.vectorPushRules[category]!.push({
ruleId: KEYWORD_RULE_ID,
description: _t("Messages containing keywords"),
vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState,
@ -540,8 +540,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
private async setKeywords(keywords: string[], originalRules: IAnnotatedPushRule[]): Promise<void> {
try {
// De-duplicate and remove empties
keywords = Array.from(new Set(keywords)).filter((k) => !!k);
const oldKeywords = Array.from(new Set(originalRules.map((r) => r.pattern))).filter((k) => !!k);
keywords = filterBoolean(Array.from(new Set(keywords)));
const oldKeywords = filterBoolean(Array.from(new Set(originalRules.map((r) => r.pattern))));
// Note: Technically because of the UI interaction (at the time of writing), the diff
// will only ever be +/-1 so we don't really have to worry about efficiently handling
@ -555,13 +555,13 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
}
}
let ruleVectorState = this.state.vectorKeywordRuleInfo.vectorState;
let ruleVectorState = this.state.vectorKeywordRuleInfo?.vectorState;
if (ruleVectorState === VectorState.Off) {
// When the current global keywords rule is OFF, we need to look at
// the flavor of existing rules to apply the same actions
// when creating the new rule.
if (originalRules.length) {
ruleVectorState = PushRuleVectorState.contentRuleVectorStateKind(originalRules[0]);
ruleVectorState = PushRuleVectorState.contentRuleVectorStateKind(originalRules[0]) ?? undefined;
} else {
ruleVectorState = VectorState.On; // default
}
@ -776,7 +776,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
/>
);
const fieldsetRows = this.state.vectorPushRules[category]?.map((r) => (
const fieldsetRows = this.state.vectorPushRules?.[category]?.map((r) => (
<fieldset
key={category + r.ruleId}
data-testid={category + r.ruleId}
@ -840,7 +840,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
</tr>
));
if (!rows.length) return null; // no targets to show
if (!rows?.length) return null; // no targets to show
return (
<div className="mx_UserNotifSettings_floatingSection">

View file

@ -72,7 +72,7 @@ interface IProps {
interface IState {
defaultIdServer?: string;
currentClientIdServer: string;
currentClientIdServer?: string;
idServer?: string;
error?: string;
busy: boolean;

View file

@ -402,14 +402,14 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
{banned.map((member) => {
const banEvent = member.events.member?.getContent();
const sender = room?.getMember(member.events.member.getSender());
let bannedBy = member.events.member.getSender(); // start by falling back to mxid
let bannedBy = member.events.member?.getSender(); // start by falling back to mxid
if (sender) bannedBy = sender.name;
return (
<BannedUser
key={member.userId}
canUnban={canBanUsers}
member={member}
reason={banEvent.reason}
reason={banEvent?.reason}
by={bannedBy}
/>
);

View file

@ -61,7 +61,7 @@ interface IProps {
interface IState {
language: string;
spellCheckEnabled: boolean;
spellCheckEnabled?: boolean;
spellCheckLanguages: string[];
haveIdServer: boolean;
serverSupportsSeparateAddAndBind?: boolean;
@ -406,7 +406,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
<div className="mx_SettingsTab_section mx_SettingsTab_section_spellcheck">
<span className="mx_SettingsTab_subheading">
{_t("Spell check")}
<ToggleSwitch checked={this.state.spellCheckEnabled} onChange={this.onSpellCheckEnabledChange} />
<ToggleSwitch checked={!!this.state.spellCheckEnabled} onChange={this.onSpellCheckEnabledChange} />
</span>
{this.state.spellCheckEnabled && !IS_MAC && (
<SpellCheckSettings

View file

@ -56,7 +56,7 @@ const confirmSignOut = async (sessionsToSignOutCount: number): Promise<boolean>
});
const [confirmed] = await finished;
return confirmed;
return !!confirmed;
};
const useSignOut = (

View file

@ -61,7 +61,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
private refreshMediaDevices = async (stream?: MediaStream): Promise<void> => {
this.setState({
mediaDevices: await MediaDeviceHandler.getDevices(),
mediaDevices: (await MediaDeviceHandler.getDevices()) ?? null,
[MediaDeviceKindEnum.AudioOutput]: MediaDeviceHandler.getAudioOutput(),
[MediaDeviceKindEnum.AudioInput]: MediaDeviceHandler.getAudioInput(),
[MediaDeviceKindEnum.VideoInput]: MediaDeviceHandler.getVideoInput(),
@ -105,8 +105,8 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
}
private renderDropdown(kind: MediaDeviceKindEnum, label: string): ReactNode {
const devices = this.state.mediaDevices[kind].slice(0);
if (devices.length === 0) return null;
const devices = this.state.mediaDevices?.[kind].slice(0);
if (!devices?.length) return null;
const defaultDevice = MediaDeviceHandler.getDefaultDevice(devices);
return (

View file

@ -77,7 +77,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
const device = await cli.getDevice(request.channel.deviceId);
const ip = device.last_seen_ip;
this.setState({
device: cli.getStoredDevice(cli.getUserId()!, request.channel.deviceId),
device: cli.getStoredDevice(cli.getUserId()!, request.channel.deviceId) ?? undefined,
ip,
});
}
@ -118,7 +118,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
should_peek: false,
metricsTrigger: "VerificationRequest",
});
const member = cli.getUser(request.otherUserId);
const member = cli.getUser(request.otherUserId) ?? undefined;
RightPanelStore.instance.setCards(
[
{ phase: RightPanelPhases.RoomSummary },

View file

@ -158,14 +158,14 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
// non-blank labels for the devices.
let stream: MediaStream | null = null;
try {
if (devices.audioinput.length > 0) {
if (devices!.audioinput.length > 0) {
// Holding just an audio stream will be enough to get us all device labels, so
// if video is muted, don't bother requesting video.
stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: !videoMuted && devices.videoinput.length > 0 && { deviceId: videoInputId },
video: !videoMuted && devices!.videoinput.length > 0 && { deviceId: videoInputId },
});
} else if (devices.videoinput.length > 0) {
} else if (devices!.videoinput.length > 0) {
// We have to resort to a video stream, even if video is supposed to be muted.
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoInputId } });
}
@ -182,7 +182,7 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
stream = null;
}
return [stream, devices.audioinput, devices.videoinput];
return [stream, devices?.audioinput ?? [], devices?.videoinput ?? []];
},
[videoInputId, videoMuted],
[null, [], []],

View file

@ -22,7 +22,7 @@ import { Action } from "../actions";
export interface ViewRoomErrorPayload extends Pick<ActionPayload, "action"> {
action: Action.ViewRoomError;
// eslint-disable-next-line camelcase
room_id: Room["roomId"];
room_id: Room["roomId"] | null;
// eslint-disable-next-line camelcase
room_alias?: string;
err?: MatrixError;

View file

@ -47,7 +47,7 @@ export default class HistoryManager {
this.removedSinceLastPush = false;
}
private shouldPush(inputType: string, diff: IDiff): boolean {
private shouldPush(inputType?: string, diff?: IDiff): boolean {
// right now we can only push a step after
// the input has been applied to the model,
// so we can't push the state before something happened.
@ -102,7 +102,7 @@ export default class HistoryManager {
}
// needs to persist parts and caret position
public tryPush(model: EditorModel, caret: Caret, inputType: string, diff: IDiff): boolean {
public tryPush(model: EditorModel, caret: Caret, inputType?: string, diff?: IDiff): boolean {
// ignore state restoration echos.
// these respect the inputType values of the input event,
// but are actually passed in from MessageEditor calling model.reset()

View file

@ -171,7 +171,7 @@ export default class EditorModel {
this._autoComplete = null;
this.autoCompletePartIdx = null;
}
this.updateCallback(caret, inputType);
this.updateCallback?.(caret, inputType);
}
/**

View file

@ -119,7 +119,7 @@ export default class AutoRageshakeStore extends AsyncStoreWithClient<IState> {
room_id: ev.getRoomId(),
session_id: sessionId,
device_id: wireContent.device_id,
user_id: ev.getSender(),
user_id: ev.getSender()!,
sender_key: wireContent.sender_key,
};

View file

@ -62,14 +62,14 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
): void => {
if (this.modalInstance) return;
this.openSourceWidgetId = sourceWidget.id;
this.openSourceWidgetRoomId = widgetRoomId;
this.openSourceWidgetRoomId = widgetRoomId ?? null;
this.modalInstance = Modal.createDialog(
ModalWidgetDialog,
{
widgetDefinition: { ...requestData },
widgetRoomId,
sourceWidgetId: sourceWidget.id,
onFinished: (success: boolean, data?: IModalWidgetReturnData) => {
onFinished: (success, data) => {
if (!success) {
this.closeModalWidget(sourceWidget, widgetRoomId, { "m.exited": true });
} else {
@ -81,13 +81,17 @@ export class ModalWidgetStore extends AsyncStoreWithClient<IState> {
this.modalInstance = null;
},
},
null,
undefined,
/* priority = */ false,
/* static = */ true,
);
};
public closeModalWidget = (sourceWidget: Widget, widgetRoomId?: string, data?: IModalWidgetReturnData): void => {
public closeModalWidget = (
sourceWidget: Widget,
widgetRoomId: string | undefined,
data: IModalWidgetReturnData,
): void => {
if (!this.modalInstance) return;
if (this.openSourceWidgetId === sourceWidget.id && this.openSourceWidgetRoomId === widgetRoomId) {
this.openSourceWidgetId = null;

View file

@ -50,4 +50,4 @@ export class RoomScrollStateStore {
if (window.mxRoomScrollStateStore === undefined) {
window.mxRoomScrollStateStore = new RoomScrollStateStore();
}
export default window.mxRoomScrollStateStore;
export default window.mxRoomScrollStateStore!;

View file

@ -395,7 +395,6 @@ export class RoomViewStore extends EventEmitter {
roomId: payload.room_id,
initialEventId: null,
initialEventPixelOffset: null,
isInitialEventHighlighted: null,
initialEventScrollIntoView: true,
roomAlias: null,
roomLoading: true,
@ -567,13 +566,13 @@ export class RoomViewStore extends EventEmitter {
}
}
private getInvitingUserId(roomId: string): string {
private getInvitingUserId(roomId: string): string | undefined {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
if (room?.getMyMembership() === "invite") {
const myMember = room.getMember(cli.getUserId());
const myMember = room.getMember(cli.getSafeUserId());
const inviteEvent = myMember ? myMember.events.member : null;
return inviteEvent && inviteEvent.getSender();
return inviteEvent?.getSender();
}
}

View file

@ -278,28 +278,28 @@ export default class RightPanelStore extends ReadyWatchingStore {
// (A nicer fix could be to indicate, that the right panel is loading if there is missing state data and re-emit if the data is available)
switch (card.phase) {
case RightPanelPhases.ThreadView:
if (!card.state.threadHeadEvent) {
if (!card.state?.threadHeadEvent) {
logger.warn("removed card from right panel because of missing threadHeadEvent in card state");
}
return !!card.state.threadHeadEvent;
return !!card.state?.threadHeadEvent;
case RightPanelPhases.RoomMemberInfo:
case RightPanelPhases.SpaceMemberInfo:
case RightPanelPhases.EncryptionPanel:
if (!card.state.member) {
if (!card.state?.member) {
logger.warn("removed card from right panel because of missing member in card state");
}
return !!card.state.member;
return !!card.state?.member;
case RightPanelPhases.Room3pidMemberInfo:
case RightPanelPhases.Space3pidMemberInfo:
if (!card.state.memberInfoEvent) {
if (!card.state?.memberInfoEvent) {
logger.warn("removed card from right panel because of missing memberInfoEvent in card state");
}
return !!card.state.memberInfoEvent;
return !!card.state?.memberInfoEvent;
case RightPanelPhases.Widget:
if (!card.state.widgetId) {
if (!card.state?.widgetId) {
logger.warn("removed card from right panel because of missing widgetId in card state");
}
return !!card.state.widgetId;
return !!card.state?.widgetId;
}
return true;
}

View file

@ -168,7 +168,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
return this.rootSpaces;
}
public get activeSpace(): SpaceKey | undefined {
public get activeSpace(): SpaceKey {
return this._activeSpace;
}
@ -1018,7 +1018,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private onRoomStateMembers = (ev: MatrixEvent): void => {
const room = this.matrixClient.getRoom(ev.getRoomId());
const userId = ev.getStateKey();
const userId = ev.getStateKey()!;
if (
room?.isSpaceRoom() && // only consider space rooms
DMRoomMap.shared().getDMRoomsForUserId(userId).length > 0 && // only consider members we have a DM with
@ -1049,9 +1049,9 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
private onRoomFavouriteChange(room: Room): void {
if (this.enabledMetaSpaces.includes(MetaSpace.Favourites)) {
if (room.tags[DefaultTagID.Favourite]) {
this.roomIdsBySpace.get(MetaSpace.Favourites).add(room.roomId);
this.roomIdsBySpace.get(MetaSpace.Favourites)?.add(room.roomId);
} else {
this.roomIdsBySpace.get(MetaSpace.Favourites).delete(room.roomId);
this.roomIdsBySpace.get(MetaSpace.Favourites)?.delete(room.roomId);
}
this.emit(MetaSpace.Favourites);
}
@ -1064,7 +1064,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
const homeRooms = this.roomIdsBySpace.get(MetaSpace.Home);
if (this.showInHomeSpace(room)) {
homeRooms?.add(room.roomId);
} else if (!this.roomIdsBySpace.get(MetaSpace.Orphans).has(room.roomId)) {
} else if (!this.roomIdsBySpace.get(MetaSpace.Orphans)?.has(room.roomId)) {
this.roomIdsBySpace.get(MetaSpace.Home)?.delete(room.roomId);
}
@ -1076,7 +1076,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}
if (enabledMetaSpaces.has(MetaSpace.Orphans) || enabledMetaSpaces.has(MetaSpace.Home)) {
if (isDm && this.roomIdsBySpace.get(MetaSpace.Orphans).delete(room.roomId)) {
if (isDm && this.roomIdsBySpace.get(MetaSpace.Orphans)?.delete(room.roomId)) {
this.emit(MetaSpace.Orphans);
this.emit(MetaSpace.Home);
}

View file

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { defer } from "matrix-js-sdk/src/utils";
import React from "react";
import BaseDialog from "../../../components/views/dialogs/BaseDialog";
@ -46,11 +45,7 @@ export const ConfirmListenBroadcastStopCurrentDialog: React.FC<Props> = ({ onFin
};
export const showConfirmListenBroadcastStopCurrentDialog = async (): Promise<boolean> => {
const { promise, resolve } = defer<boolean>();
Modal.createDialog(ConfirmListenBroadcastStopCurrentDialog, {
onFinished: resolve,
});
return promise;
const { finished } = Modal.createDialog(ConfirmListenBroadcastStopCurrentDialog);
const [confirmed] = await finished;
return !!confirmed;
};

View file

@ -35,7 +35,7 @@ export const useVoiceBroadcastPlayback = (
position: number;
timeLeft: number;
};
sender: RoomMember;
sender: RoomMember | null;
liveness: VoiceBroadcastLiveness;
playbackState: VoiceBroadcastPlaybackState;
toggle(): void;

View file

@ -44,7 +44,7 @@ const showStopBroadcastingDialog = async (): Promise<boolean> => {
button: _t("Yes, stop broadcast"),
});
const [confirmed] = await finished;
return confirmed;
return !!confirmed;
};
export const useVoiceBroadcastRecording = (
@ -54,7 +54,7 @@ export const useVoiceBroadcastRecording = (
timeLeft: number;
recordingState: VoiceBroadcastRecordingState;
room: Room;
sender: RoomMember;
sender: RoomMember | null;
stopRecording(): void;
toggleRecording(): void;
} => {

View file

@ -32,7 +32,7 @@ export * from "./components/atoms/VoiceBroadcastHeader";
export * from "./components/atoms/VoiceBroadcastPlaybackControl";
export * from "./components/atoms/VoiceBroadcastRecordingConnectionError";
export * from "./components/atoms/VoiceBroadcastRoomSubtitle";
export * from "./components/molecules/ConfirmListeBroadcastStopCurrent";
export * from "./components/molecules/ConfirmListenBroadcastStopCurrent";
export * from "./components/molecules/VoiceBroadcastPlaybackBody";
export * from "./components/molecules/VoiceBroadcastSmallPlaybackBody";
export * from "./components/molecules/VoiceBroadcastPreRecordingPip";

View file

@ -674,7 +674,7 @@ describe("MessagePanel", function () {
// Increase the length of the loop here to test performance issues with
// rendering
const events = [];
const events: MatrixEvent[] = [];
for (let i = 0; i < 100; i++) {
events.push(
TestUtilsMatrix.mkEvent({

View file

@ -35,6 +35,7 @@ import RightPanelStore from "../../../src/stores/right-panel/RightPanelStore";
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
import { WidgetLayoutStore } from "../../../src/stores/widgets/WidgetLayoutStore";
import { SdkContextClass } from "../../../src/contexts/SDKContext";
import { RoomPermalinkCreator } from "../../../src/utils/permalinks/Permalinks";
const RightPanelBase = wrapInMatrixClientContext(_RightPanel);
@ -110,7 +111,13 @@ describe("RightPanel", () => {
});
await viewedRoom;
const { container } = render(<RightPanel room={r1} resizeNotifier={resizeNotifier} />);
const { container } = render(
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
);
expect(container.getElementsByClassName("mx_RoomSummaryCard")).toHaveLength(1);
const switchedPhases = waitForRpsUpdate();
@ -152,7 +159,13 @@ describe("RightPanel", () => {
await spinUpStores();
// Run initial render with room 1, and also running lifecycle methods
const { container, rerender } = render(<RightPanel room={r1} resizeNotifier={resizeNotifier} />);
const { container, rerender } = render(
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>,
);
// Wait for RPS room 1 updates to fire
const rpsUpdated = waitForRpsUpdate();
dis.dispatch({
@ -172,7 +185,13 @@ describe("RightPanel", () => {
room_id: "r2",
});
await _rpsUpdated;
rerender(<RightPanel room={r2} resizeNotifier={resizeNotifier} />);
rerender(
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>,
);
// After all that setup, now to the interesting part...
// We want to verify that as we change to room 2, we should always have

View file

@ -50,6 +50,7 @@ import { ElementWidgetCapabilities } from "../../../../src/stores/widgets/Elemen
import { ElementWidget } from "../../../../src/stores/widgets/StopGapWidget";
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
import { ModuleRunner } from "../../../../src/modules/ModuleRunner";
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
describe("AppTile", () => {
let cli: MatrixClient;
@ -153,7 +154,11 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
@ -178,7 +183,11 @@ describe("AppTile", () => {
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>,
);
@ -214,7 +223,11 @@ describe("AppTile", () => {
// Run initial render with room 1, and also running lifecycle methods
const renderResult = render(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r1} resizeNotifier={resizeNotifier} />
<RightPanel
room={r1}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r1, r1.roomId)}
/>
</MatrixClientContext.Provider>,
);
// Wait for RPS room 1 updates to fire
@ -256,7 +269,11 @@ describe("AppTile", () => {
});
renderResult.rerender(
<MatrixClientContext.Provider value={cli}>
<RightPanel room={r2} resizeNotifier={resizeNotifier} />
<RightPanel
room={r2}
resizeNotifier={resizeNotifier}
permalinkCreator={new RoomPermalinkCreator(r2, r2.roomId)}
/>
</MatrixClientContext.Provider>,
);
await rpsUpdated2;

View file

@ -68,7 +68,7 @@ describe("PreferencesUserSettingsTab", () => {
const expectSetValueToHaveBeenCalled = (
name: string,
roomId: string | undefined,
roomId: string | null,
level: SettingLevel,
value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);