Conform more of the codebase to strictNullChecks (#10607)

* Conform more of the codebase to `strictNullChecks`

* Conform more of the codebase to `strictNullChecks`

* Fix types

* Conform more of the codebase to `strictNullChecks`

* Conform more of the codebase to `strictNullChecks`
This commit is contained in:
Michael Telatynski 2023-04-17 09:25:00 +01:00 committed by GitHub
parent 9d8d610f31
commit 56e4ae41f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 71 additions and 68 deletions

View file

@ -327,7 +327,11 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
private shouldHideSender(): boolean { private shouldHideSender(): boolean {
return this.props.room?.getInvitedAndJoinedMemberCount() <= 2 && this.props.layout === Layout.Bubble; return (
!!this.props.room &&
this.props.room.getInvitedAndJoinedMemberCount() <= 2 &&
this.props.layout === Layout.Bubble
);
} }
private calculateRoomMembersCount = (): void => { private calculateRoomMembersCount = (): void => {
@ -465,7 +469,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
} }
if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender())) { if (MatrixClientPeg.get().isUserIgnored(mxEv.getSender()!)) {
return false; // ignored = no show (only happens if the ignore happens after an event was received) return false; // ignored = no show (only happens if the ignore happens after an event was received)
} }
@ -647,7 +651,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
for (let i = 0; i < events.length; i++) { for (let i = 0; i < events.length; i++) {
const eventAndShouldShow = events[i]; const eventAndShouldShow = events[i];
const { event, shouldShow } = eventAndShouldShow; const { event, shouldShow } = eventAndShouldShow;
const eventId = event.getId(); const eventId = event.getId()!;
const last = event === lastShownEvent; const last = event === lastShownEvent;
const { nextEventAndShouldShow, nextTile } = this.getNextEventInfo(events, i); const { nextEventAndShouldShow, nextTile } = this.getNextEventInfo(events, i);
@ -745,7 +749,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
!wantsDateSeparator && !wantsDateSeparator &&
shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents, this.context.timelineRenderingType); shouldFormContinuation(prevEvent, mxEv, this.showHiddenEvents, this.context.timelineRenderingType);
const eventId = mxEv.getId(); const eventId = mxEv.getId()!;
const highlight = eventId === this.props.highlightedEventId; const highlight = eventId === this.props.highlightedEventId;
const readReceipts = this.readReceiptsByEvent.get(eventId); const readReceipts = this.readReceiptsByEvent.get(eventId);
@ -1075,7 +1079,7 @@ abstract class BaseGrouper {
public readonly nextEventTile?: MatrixEvent | null, public readonly nextEventTile?: MatrixEvent | null,
) { ) {
this.readMarker = panel.readMarkerForEvent( this.readMarker = panel.readMarkerForEvent(
firstEventAndShouldShow.event.getId(), firstEventAndShouldShow.event.getId()!,
firstEventAndShouldShow.event === lastShownEvent, firstEventAndShouldShow.event === lastShownEvent,
); );
} }
@ -1143,7 +1147,7 @@ class CreationGrouper extends BaseGrouper {
public add({ event: ev, shouldShow }: EventAndShouldShow): void { public add({ event: ev, shouldShow }: EventAndShouldShow): void {
const panel = this.panel; const panel = this.panel;
this.readMarker = this.readMarker || panel.readMarkerForEvent(ev.getId(), ev === this.lastShownEvent); this.readMarker = this.readMarker || panel.readMarkerForEvent(ev.getId()!, ev === this.lastShownEvent);
if (!shouldShow) { if (!shouldShow) {
return; return;
} }
@ -1295,7 +1299,7 @@ class MainGrouper extends BaseGrouper {
// We can ignore any events that don't actually have a message to display // We can ignore any events that don't actually have a message to display
if (!hasText(ev, this.panel.showHiddenEvents)) return; if (!hasText(ev, this.panel.showHiddenEvents)) return;
} }
this.readMarker = this.readMarker || this.panel.readMarkerForEvent(ev.getId(), ev === this.lastShownEvent); this.readMarker = this.readMarker || this.panel.readMarkerForEvent(ev.getId()!, ev === this.lastShownEvent);
if (!this.panel.showHiddenEvents && !shouldShow) { if (!this.panel.showHiddenEvents && !shouldShow) {
// absorb hidden events to not split the summary // absorb hidden events to not split the summary
return; return;
@ -1331,7 +1335,10 @@ class MainGrouper extends BaseGrouper {
// This will prevent it from being re-created unnecessarily, and instead will allow new props to be provided. // This will prevent it from being re-created unnecessarily, and instead will allow new props to be provided.
// In turn, the shouldComponentUpdate method on ELS can be used to prevent unnecessary renderings. // In turn, the shouldComponentUpdate method on ELS can be used to prevent unnecessary renderings.
const keyEvent = this.events.find((e) => this.panel.grouperKeyMap.get(e)); const keyEvent = this.events.find((e) => this.panel.grouperKeyMap.get(e));
const key = keyEvent ? this.panel.grouperKeyMap.get(keyEvent) : this.generateKey(); const key =
keyEvent && this.panel.grouperKeyMap.has(keyEvent)
? this.panel.grouperKeyMap.get(keyEvent)!
: this.generateKey();
if (!keyEvent) { if (!keyEvent) {
// Populate the weak map with the key. // Populate the weak map with the key.
// Note that we only set the key on the specific event it refers to, since this group might get // Note that we only set the key on the specific event it refers to, since this group might get

View file

@ -182,7 +182,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
{ {
key: "number", key: "number",
test: ({ value }) => { test: ({ value }) => {
const parsedSize = parseInt(value, 10); const parsedSize = parseInt(value!, 10);
return validateNumberInRange(1, 2000)(parsedSize); return validateNumberInRange(1, 2000)(parsedSize);
}, },
invalid: () => { invalid: () => {
@ -218,7 +218,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
{ {
key: "number", key: "number",
test: ({ value }) => { test: ({ value }) => {
const parsedSize = parseInt(value, 10); const parsedSize = parseInt(value!, 10);
return validateNumberInRange(1, 10 ** 8)(parsedSize); return validateNumberInRange(1, 10 ** 8)(parsedSize);
}, },
invalid: () => { invalid: () => {

View file

@ -38,7 +38,7 @@ interface IProps {
} }
const FeedbackDialog: React.FC<IProps> = (props: IProps) => { const FeedbackDialog: React.FC<IProps> = (props: IProps) => {
const feedbackRef = useRef<Field>(); const feedbackRef = useRef<Field>(null);
const [comment, setComment] = useState<string>(""); const [comment, setComment] = useState<string>("");
const [canContact, toggleCanContact] = useStateToggle(false); const [canContact, toggleCanContact] = useStateToggle(false);

View file

@ -22,7 +22,7 @@ import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps { interface IProps {
// Current room // Current room
roomId: string; roomId: string | null;
minWidth: number; minWidth: number;
maxWidth: number; maxWidth: number;
} }

View file

@ -97,7 +97,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
<Pill <Pill
type={PillType.UserMention} type={PillType.UserMention}
room={this.props.room} room={this.props.room}
url={makeUserPermalink(content.creator)} url={makeUserPermalink(content.creator!)}
shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")} shouldShowPillAvatar={SettingsStore.getValue("Pill.shouldShowPillAvatar")}
/> />
), ),

View file

@ -213,7 +213,7 @@ export default class ChangePassword extends React.Component<IProps, IState> {
const modal = Modal.createDialog(SetEmailDialog, { const modal = Modal.createDialog(SetEmailDialog, {
title: _t("Do you want to set an email address?"), title: _t("Do you want to set an email address?"),
}); });
return modal.finished.then(([confirmed]) => confirmed); return modal.finished.then(([confirmed]) => !!confirmed);
} }
private onExportE2eKeysClicked = (): void => { private onExportE2eKeysClicked = (): void => {

View file

@ -94,9 +94,9 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
const secretStorage = cli.crypto!.secretStorage; const secretStorage = cli.crypto!.secretStorage;
const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId()); const crossSigningPublicKeysOnDevice = Boolean(crossSigning.getId());
const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage)); const crossSigningPrivateKeysInStorage = Boolean(await crossSigning.isStoredInSecretStorage(secretStorage));
const masterPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("master"))); const masterPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("master"));
const selfSigningPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("self_signing"))); const selfSigningPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("self_signing"));
const userSigningPrivateKeyCached = !!(pkCache && (await pkCache.getCrossSigningKeyCache("user_signing"))); const userSigningPrivateKeyCached = !!(await pkCache?.getCrossSigningKeyCache?.("user_signing"));
const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature( const homeserverSupportsCrossSigning = await cli.doesServerSupportUnstableFeature(
"org.matrix.e2e_cross_signing", "org.matrix.e2e_cross_signing",
); );

View file

@ -51,7 +51,7 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
super(props); super(props);
this.state = { this.state = {
renaming: false, renaming: false,
displayName: props.device.display_name, displayName: props.device.display_name ?? "",
}; };
} }
@ -103,11 +103,11 @@ export default class DevicesPanelEntry extends React.Component<IProps, IState> {
}); });
} else { } else {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const userId = cli.getUserId()!; const userId = cli.getSafeUserId();
const verificationRequestPromise = cli.requestVerification(userId, [this.props.device.device_id]); const verificationRequestPromise = cli.requestVerification(userId, [this.props.device.device_id]);
Modal.createDialog(VerificationRequestDialog, { Modal.createDialog(VerificationRequestDialog, {
verificationRequestPromise, verificationRequestPromise,
member: cli.getUser(userId), member: cli.getUser(userId) ?? undefined,
onFinished: async (): Promise<void> => { onFinished: async (): Promise<void> => {
const request = await verificationRequestPromise; const request = await verificationRequestPromise;
request.cancel(); request.cancel();

View file

@ -26,7 +26,6 @@ import EventIndexPeg from "../../../indexing/EventIndexPeg";
import { SettingLevel } from "../../../settings/SettingLevel"; import { SettingLevel } from "../../../settings/SettingLevel";
import SeshatResetDialog from "../dialogs/SeshatResetDialog"; import SeshatResetDialog from "../dialogs/SeshatResetDialog";
import InlineSpinner from "../elements/InlineSpinner"; import InlineSpinner from "../elements/InlineSpinner";
import { IIndexStats } from "../../../indexing/BaseEventIndexManager";
interface IState { interface IState {
enabling: boolean; enabling: boolean;
@ -49,15 +48,9 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
public updateCurrentRoom = async (): Promise<void> => { public updateCurrentRoom = async (): Promise<void> => {
const eventIndex = EventIndexPeg.get(); const eventIndex = EventIndexPeg.get();
let stats: IIndexStats | undefined; const stats = await eventIndex?.getStats().catch(() => {});
// This call may fail if sporadically, not a huge issue as we will try later again and probably succeed.
try { if (!stats) return;
stats = await eventIndex?.getStats();
} catch {
// This call may fail if sporadically, not a huge issue as we will
// try later again and probably succeed.
return;
}
this.setState({ this.setState({
eventIndexSize: stats.size, eventIndexSize: stats.size,
@ -88,14 +81,13 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
if (eventIndex !== null) { if (eventIndex !== null) {
eventIndex.on("changedCheckpoint", this.updateCurrentRoom); eventIndex.on("changedCheckpoint", this.updateCurrentRoom);
try { const stats = await eventIndex.getStats().catch(() => {});
const stats = await eventIndex.getStats();
eventIndexSize = stats.size;
roomCount = stats.roomCount;
} catch {
// This call may fail if sporadically, not a huge issue as we // This call may fail if sporadically, not a huge issue as we
// will try later again in the updateCurrentRoom call and // will try later again in the updateCurrentRoom call and
// probably succeed. // probably succeed.
if (stats) {
eventIndexSize = stats.size;
roomCount = stats.roomCount;
} }
} }

View file

@ -83,7 +83,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
}; };
private onValidateFontSize = async ({ value }: Pick<IFieldState, "value">): Promise<IValidationResult> => { private onValidateFontSize = async ({ value }: Pick<IFieldState, "value">): Promise<IValidationResult> => {
const parsedSize = parseFloat(value); const parsedSize = parseFloat(value!);
const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF;
const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF;
@ -98,7 +98,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
}; };
} }
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, parseInt(value, 10) - FontWatcher.SIZE_DIFF); SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, parseInt(value!, 10) - FontWatcher.SIZE_DIFF);
return { valid: true, feedback: _t("Use between %(min)s pt and %(max)s pt", { min, max }) }; return { valid: true, feedback: _t("Use between %(min)s pt and %(max)s pt", { min, max }) };
}; };

View file

@ -61,7 +61,7 @@ const JoinRuleSettings: React.FC<IProps> = ({
const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli); const disabled = !room.currentState.mayClientSendStateEvent(EventType.RoomJoinRules, cli);
const [content, setContent] = useLocalEcho<IJoinRuleEventContent>( const [content, setContent] = useLocalEcho<IJoinRuleEventContent | undefined>(
() => room.currentState.getStateEvents(EventType.RoomJoinRules, "")?.getContent(), () => room.currentState.getStateEvents(EventType.RoomJoinRules, "")?.getContent(),
(content) => cli.sendStateEvent(room.roomId, EventType.RoomJoinRules, content, ""), (content) => cli.sendStateEvent(room.roomId, EventType.RoomJoinRules, content, ""),
onError, onError,
@ -70,10 +70,10 @@ const JoinRuleSettings: React.FC<IProps> = ({
const { join_rule: joinRule = JoinRule.Invite } = content || {}; const { join_rule: joinRule = JoinRule.Invite } = content || {};
const restrictedAllowRoomIds = const restrictedAllowRoomIds =
joinRule === JoinRule.Restricted joinRule === JoinRule.Restricted
? content.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id) ? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id)
: undefined; : undefined;
const editRestrictedRoomIds = async (): Promise<string[]> => { const editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
let selected = restrictedAllowRoomIds; let selected = restrictedAllowRoomIds;
if (!selected?.length && SpaceStore.instance.activeSpaceRoom) { if (!selected?.length && SpaceStore.instance.activeSpaceRoom) {
selected = [SpaceStore.instance.activeSpaceRoom.roomId]; selected = [SpaceStore.instance.activeSpaceRoom.roomId];
@ -207,7 +207,7 @@ const JoinRuleSettings: React.FC<IProps> = ({
"Anyone in <spaceName/> can find and join. You can select other spaces too.", "Anyone in <spaceName/> can find and join. You can select other spaces too.",
{}, {},
{ {
spaceName: () => <b>{SpaceStore.instance.activeSpaceRoom.name}</b>, spaceName: () => <b>{SpaceStore.instance.activeSpaceRoom!.name}</b>,
}, },
); );
} else { } else {
@ -229,7 +229,7 @@ const JoinRuleSettings: React.FC<IProps> = ({
} }
const onChange = async (joinRule: JoinRule): Promise<void> => { const onChange = async (joinRule: JoinRule): Promise<void> => {
const beforeJoinRule = content.join_rule; const beforeJoinRule = content?.join_rule;
let restrictedAllowRoomIds: string[] | undefined; let restrictedAllowRoomIds: string[] | undefined;
if (joinRule === JoinRule.Restricted) { if (joinRule === JoinRule.Restricted) {

View file

@ -31,7 +31,6 @@ import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
interface IState { interface IState {
userId?: string;
originalDisplayName: string; originalDisplayName: string;
displayName: string; displayName: string;
originalAvatarUrl: string | null; originalAvatarUrl: string | null;
@ -41,16 +40,16 @@ interface IState {
} }
export default class ProfileSettings extends React.Component<{}, IState> { export default class ProfileSettings extends React.Component<{}, IState> {
private readonly userId: string;
private avatarUpload: React.RefObject<HTMLInputElement> = createRef(); private avatarUpload: React.RefObject<HTMLInputElement> = createRef();
public constructor(props: {}) { public constructor(props: {}) {
super(props); super(props);
const client = MatrixClientPeg.get(); this.userId = MatrixClientPeg.get().getSafeUserId();
let avatarUrl = OwnProfileStore.instance.avatarMxc; let avatarUrl = OwnProfileStore.instance.avatarMxc;
if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96); if (avatarUrl) avatarUrl = mediaFromMxc(avatarUrl).getSquareThumbnailHttp(96);
this.state = { this.state = {
userId: client.getUserId()!,
originalDisplayName: OwnProfileStore.instance.displayName ?? "", originalDisplayName: OwnProfileStore.instance.displayName ?? "",
displayName: OwnProfileStore.instance.displayName ?? "", displayName: OwnProfileStore.instance.displayName ?? "",
originalAvatarUrl: avatarUrl, originalAvatarUrl: avatarUrl,
@ -150,7 +149,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (ev) => { reader.onload = (ev) => {
this.setState({ this.setState({
avatarUrl: ev.target?.result, avatarUrl: ev.target?.result ?? undefined,
avatarFile: file, avatarFile: file,
enableProfileSave: true, enableProfileSave: true,
}); });
@ -159,7 +158,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.state.userId, { const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier(this.userId, {
withDisplayName: true, withDisplayName: true,
}); });
@ -198,7 +197,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
</div> </div>
<AvatarSetting <AvatarSetting
avatarUrl={avatarUrl} avatarUrl={avatarUrl}
avatarName={this.state.displayName || this.state.userId} avatarName={this.state.displayName || this.userId}
avatarAltText={_t("Profile picture")} avatarAltText={_t("Profile picture")}
uploadAvatar={this.uploadAvatar} uploadAvatar={this.uploadAvatar}
removeAvatar={this.removeAvatar} removeAvatar={this.removeAvatar}

View file

@ -379,12 +379,12 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
return <div key={i}>{sigStatus}</div>; return <div key={i}>{sigStatus}</div>;
}); });
if (backupSigStatus.sigs.length === 0) { if (!backupSigStatus?.sigs?.length) {
backupSigStatuses = _t("Backup is not signed by any of your sessions"); backupSigStatuses = _t("Backup is not signed by any of your sessions");
} }
let trustedLocally; let trustedLocally: string | undefined;
if (backupSigStatus.trusted_locally) { if (backupSigStatus?.trusted_locally) {
trustedLocally = _t("This backup is trusted because it has been restored on this session"); trustedLocally = _t("This backup is trusted because it has been restored on this session");
} }

View file

@ -105,13 +105,13 @@ const DeviceDetails: React.FC<Props> = ({
const showPushNotificationSection = !!pusher || !!localNotificationSettings; const showPushNotificationSection = !!pusher || !!localNotificationSettings;
function isPushNotificationsEnabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean { function isPushNotificationsEnabled(pusher?: IPusher, notificationSettings?: LocalNotificationSettings): boolean {
if (pusher) return pusher[PUSHER_ENABLED.name]; if (pusher) return !!pusher[PUSHER_ENABLED.name];
if (localNotificationSettings) return !localNotificationSettings.is_silenced; if (localNotificationSettings) return !localNotificationSettings.is_silenced;
return true; return true;
} }
function isCheckboxDisabled(pusher: IPusher, notificationSettings: LocalNotificationSettings): boolean { function isCheckboxDisabled(pusher?: IPusher, notificationSettings?: LocalNotificationSettings): boolean {
if (localNotificationSettings) return false; if (localNotificationSettings) return false;
if (pusher && !supportsMSC3881) return true; if (pusher && !supportsMSC3881) return true;
return false; return false;

View file

@ -47,8 +47,8 @@ const deviceTypeLabel: Record<DeviceType, string> = {
}; };
export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, deviceType }) => { export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, deviceType }) => {
const Icon = deviceTypeIcon[deviceType] || deviceTypeIcon[DeviceType.Unknown]; const Icon = deviceTypeIcon[deviceType!] || deviceTypeIcon[DeviceType.Unknown];
const label = deviceTypeLabel[deviceType] || deviceTypeLabel[DeviceType.Unknown]; const label = deviceTypeLabel[deviceType!] || deviceTypeLabel[DeviceType.Unknown];
return ( return (
<div <div
className={classNames("mx_DeviceTypeIcon", { className={classNames("mx_DeviceTypeIcon", {

View file

@ -212,9 +212,9 @@ const SpaceCreateMenu: React.FC<{
const [busy, setBusy] = useState<boolean>(false); const [busy, setBusy] = useState<boolean>(false);
const [name, setName] = useState(""); const [name, setName] = useState("");
const spaceNameField = useRef<Field>(); const spaceNameField = useRef<Field>(null);
const [alias, setAlias] = useState(""); const [alias, setAlias] = useState("");
const spaceAliasField = useRef<RoomAliasField>(); const spaceAliasField = useRef<RoomAliasField>(null);
const [avatar, setAvatar] = useState<File | undefined>(undefined); const [avatar, setAvatar] = useState<File | undefined>(undefined);
const [topic, setTopic] = useState<string>(""); const [topic, setTopic] = useState<string>("");

View file

@ -331,9 +331,9 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(
const SpacePanel: React.FC = () => { const SpacePanel: React.FC = () => {
const [isPanelCollapsed, setPanelCollapsed] = useState(true); const [isPanelCollapsed, setPanelCollapsed] = useState(true);
const ref = useRef<HTMLDivElement>(); const ref = useRef<HTMLDivElement>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
UIStore.instance.trackElementDimensions("SpacePanel", ref.current); if (ref.current) UIStore.instance.trackElementDimensions("SpacePanel", ref.current);
return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel"); return () => UIStore.instance.stopTrackingElementDimensions("SpacePanel");
}, []); }, []);

View file

@ -553,6 +553,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}; };
private rebuildSpaceHierarchy = (): void => { private rebuildSpaceHierarchy = (): void => {
if (!this.matrixClient) return;
const visibleSpaces = this.matrixClient const visibleSpaces = this.matrixClient
.getVisibleRooms(this._msc3946ProcessDynamicPredecessor) .getVisibleRooms(this._msc3946ProcessDynamicPredecessor)
.filter((r) => r.isSpaceRoom()); .filter((r) => r.isSpaceRoom());
@ -589,6 +590,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}; };
private rebuildParentMap = (): void => { private rebuildParentMap = (): void => {
if (!this.matrixClient) return;
const joinedSpaces = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor).filter((r) => { const joinedSpaces = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor).filter((r) => {
return r.isSpaceRoom() && r.getMyMembership() === "join"; return r.isSpaceRoom() && r.getMyMembership() === "join";
}); });
@ -624,6 +626,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}; };
private rebuildMetaSpaces = (): void => { private rebuildMetaSpaces = (): void => {
if (!this.matrixClient) return;
const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor);
@ -658,6 +661,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}; };
private updateNotificationStates = (spaces?: SpaceKey[]): void => { private updateNotificationStates = (spaces?: SpaceKey[]): void => {
if (!this.matrixClient) return;
const enabledMetaSpaces = new Set(this.enabledMetaSpaces); const enabledMetaSpaces = new Set(this.enabledMetaSpaces);
const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor);
@ -745,6 +749,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
}; };
private onRoomsUpdate = (): void => { private onRoomsUpdate = (): void => {
if (!this.matrixClient) return;
const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor); const visibleRooms = this.matrixClient.getVisibleRooms(this._msc3946ProcessDynamicPredecessor);
const prevRoomsBySpace = this.roomIdsBySpace; const prevRoomsBySpace = this.roomIdsBySpace;

View file

@ -133,7 +133,7 @@ describe("ThreadPanel", () => {
jest.spyOn(mockClient, "getRoom").mockReturnValue(room); jest.spyOn(mockClient, "getRoom").mockReturnValue(room);
await room.createThreadsTimelineSets(); await room.createThreadsTimelineSets();
const [allThreads, myThreads] = room.threadsTimelineSets; const [allThreads, myThreads] = room.threadsTimelineSets;
jest.spyOn(room, "createThreadsTimelineSets").mockReturnValue(Promise.resolve([allThreads, myThreads])); jest.spyOn(room, "createThreadsTimelineSets").mockReturnValue(Promise.resolve([allThreads!, myThreads!]));
}); });
function toggleThreadFilter(container: HTMLElement, newFilter: ThreadFilterType) { function toggleThreadFilter(container: HTMLElement, newFilter: ThreadFilterType) {
@ -195,11 +195,11 @@ describe("ThreadPanel", () => {
return event ? Promise.resolve(event) : Promise.reject(); return event ? Promise.resolve(event) : Promise.reject();
}); });
const [allThreads, myThreads] = room.threadsTimelineSets; const [allThreads, myThreads] = room.threadsTimelineSets;
allThreads.addLiveEvent(otherThread.rootEvent); allThreads!.addLiveEvent(otherThread.rootEvent);
allThreads.addLiveEvent(mixedThread.rootEvent); allThreads!.addLiveEvent(mixedThread.rootEvent);
allThreads.addLiveEvent(ownThread.rootEvent); allThreads!.addLiveEvent(ownThread.rootEvent);
myThreads.addLiveEvent(mixedThread.rootEvent); myThreads!.addLiveEvent(mixedThread.rootEvent);
myThreads.addLiveEvent(ownThread.rootEvent); myThreads!.addLiveEvent(ownThread.rootEvent);
let events: EventData[] = []; let events: EventData[] = [];
const renderResult = render(<TestThreadPanel />); const renderResult = render(<TestThreadPanel />);
@ -245,7 +245,7 @@ describe("ThreadPanel", () => {
return event ? Promise.resolve(event) : Promise.reject(); return event ? Promise.resolve(event) : Promise.reject();
}); });
const [allThreads] = room.threadsTimelineSets; const [allThreads] = room.threadsTimelineSets;
allThreads.addLiveEvent(otherThread.rootEvent); allThreads!.addLiveEvent(otherThread.rootEvent);
let events: EventData[] = []; let events: EventData[] = [];
const renderResult = render(<TestThreadPanel />); const renderResult = render(<TestThreadPanel />);