Merge remote-tracking branch 'origin/develop' into feat/emoji-picker-rich-text-mode

This commit is contained in:
Florian Duros 2022-12-05 17:40:46 +01:00
commit 54e12d265b
No known key found for this signature in database
GPG key ID: 9700AA5870258A0B
139 changed files with 2830 additions and 3202 deletions

View file

@ -127,7 +127,7 @@ export default class InteractiveAuthComponent extends React.Component<IProps, IS
});
if (this.props.poll) {
this.intervalId = setInterval(() => {
this.intervalId = window.setInterval(() => {
this.authLogic.poll();
}, 2000);
}

View file

@ -1965,7 +1965,7 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
this.accountPassword = password;
// self-destruct the password after 5mins
if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer);
this.accountPasswordTimer = setTimeout(() => {
this.accountPasswordTimer = window.setTimeout(() => {
this.accountPassword = null;
this.accountPasswordTimer = null;
}, 60 * 5 * 1000);

View file

@ -459,7 +459,7 @@ export default class ScrollPanel extends React.Component<IProps> {
if (this.unfillDebouncer) {
clearTimeout(this.unfillDebouncer);
}
this.unfillDebouncer = setTimeout(() => {
this.unfillDebouncer = window.setTimeout(() => {
this.unfillDebouncer = null;
debuglog("unfilling now", { backwards, origExcessHeight });
this.props.onUnfillRequest?.(backwards, markerScrollToken!);
@ -485,7 +485,7 @@ export default class ScrollPanel extends React.Component<IProps> {
// this will block the scroll event handler for +700ms
// if messages are already cached in memory,
// This would cause jumping to happen on Chrome/macOS.
return new Promise(resolve => setTimeout(resolve, 1)).then(() => {
return new Promise(resolve => window.setTimeout(resolve, 1)).then(() => {
return this.props.onFillRequest(backwards);
}).finally(() => {
this.pendingFillRequests[dir] = false;

View file

@ -16,7 +16,7 @@ limitations under the License.
import React from 'react';
import { Room } from "matrix-js-sdk/src/models/room";
import filesize from "filesize";
import { filesize } from "filesize";
import { IEventRelation } from 'matrix-js-sdk/src/matrix';
import { Optional } from "matrix-events-sdk";

View file

@ -96,8 +96,9 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
private async updateMode(mode: Mode) {
this.setState({ phase: Phase.Loading });
if (this.state.rendezvous) {
this.state.rendezvous.onFailure = undefined;
await this.state.rendezvous.cancel(RendezvousFailureReason.UserCancelled);
const rendezvous = this.state.rendezvous;
rendezvous.onFailure = undefined;
await rendezvous.cancel(RendezvousFailureReason.UserCancelled);
this.setState({ rendezvous: undefined });
}
if (mode === Mode.Show) {

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { _t } from '../../../languageHandler';
@ -27,11 +26,6 @@ interface IProps extends IContextMenuProps {
}
export default class LegacyCallContextMenu extends React.Component<IProps> {
static propTypes = {
// js-sdk User object. Not required because it might not exist.
user: PropTypes.object,
};
constructor(props) {
super(props);
}

View file

@ -697,7 +697,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.debounceTimer = window.setTimeout(() => {
this.updateSuggestions(term);
}, 150); // 150ms debounce (human reaction time + some)
};

View file

@ -48,7 +48,7 @@ async function syncHealthCheck(cli: MatrixClient): Promise<void> {
*/
async function proxyHealthCheck(endpoint: string, hsUrl?: string): Promise<void> {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 10 * 1000); // 10s
const id = window.setTimeout(() => controller.abort(), 10 * 1000); // 10s
const res = await fetch(endpoint + "/client/server.json", {
signal: controller.signal,
});

View file

@ -16,7 +16,7 @@ limitations under the License.
*/
import React from 'react';
import filesize from "filesize";
import { filesize } from "filesize";
import { Icon as FileIcon } from '../../../../res/img/feather-customised/files.svg';
import { _t } from '../../../languageHandler';

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import filesize from 'filesize';
import { filesize } from 'filesize';
import React from 'react';
import { _t } from '../../../languageHandler';

View file

@ -51,7 +51,7 @@ const VerificationRequestExplorer: React.FC<{
if (request.timeout == 0) return;
/* Note that request.timeout is a getter, so its value changes */
const id = setInterval(() => {
const id = window.setInterval(() => {
setRequestTimeout(request.timeout);
}, 500);

View file

@ -228,7 +228,7 @@ export const useWebSearchMetrics = (numResults: number, queryLength: number, via
if (!queryLength) return;
// send metrics after a 1s debounce
const timeoutId = setTimeout(() => {
const timeoutId = window.setTimeout(() => {
PosthogAnalytics.instance.trackEvent<WebSearchEvent>({
eventName: "WebSearch",
viaSpotlight,

View file

@ -106,7 +106,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<
}
async componentDidMount() {
// setInterval() first waits and then executes, therefore
// window.setInterval() first waits and then executes, therefore
// we call getDesktopCapturerSources() here without any delay.
// Otherwise the dialog would be left empty for some time.
this.setState({
@ -114,7 +114,7 @@ export default class DesktopCapturerSourcePicker extends React.Component<
});
// We update the sources every 500ms to get newer thumbnails
this.interval = setInterval(async () => {
this.interval = window.setInterval(async () => {
this.setState({
sources: await getDesktopCapturerSources(),
});

View file

@ -240,7 +240,7 @@ export default class ReplyChain extends React.Component<IProps, IState> {
{ _t("In reply to <a>this message</a>",
{},
{ a: (sub) => (
<a className="mx_reply_anchor" href={`#${eventId}`} scroll-to={eventId}> { sub } </a>
<a className="mx_reply_anchor" href={`#${eventId}`} data-scroll-to={eventId}> { sub } </a>
),
})
}

View file

@ -80,12 +80,13 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
if (!canChange && this.props.hideIfCannotSet) return null;
const label = this.props.label
const label = (this.props.label
? _t(this.props.label)
: SettingsStore.getDisplayName(this.props.name, this.props.level);
: SettingsStore.getDisplayName(this.props.name, this.props.level)) ?? undefined;
const description = SettingsStore.getDescription(this.props.name);
const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name);
let disabledDescription: JSX.Element;
let disabledDescription: JSX.Element | null = null;
if (this.props.disabled && this.props.disabledDescription) {
disabledDescription = <div className="mx_SettingsFlag_microcopy">
{ this.props.disabledDescription }
@ -106,7 +107,20 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
<label className="mx_SettingsFlag_label">
<span className="mx_SettingsFlag_labelText">{ label }</span>
{ description && <div className="mx_SettingsFlag_microcopy">
{ description }
{ shouldWarn
? _t(
"<w>WARNING:</w> <description/>", {},
{
"w": (sub) => (
<span className="mx_SettingsTab_microcopy_warning">
{ sub }
</span>
),
"description": description,
},
)
: description
}
</div> }
{ disabledDescription }
</label>

View file

@ -35,7 +35,7 @@ export function UseCaseSelection({ onFinished }: Props) {
// Call onFinished 1.5s after `selection` becomes truthy, to give time for the animation to run
useEffect(() => {
if (selection) {
let handler: number | null = setTimeout(() => {
let handler: number | null = window.setTimeout(() => {
handler = null;
onFinished(selection);
}, TIMEOUT);

View file

@ -191,7 +191,7 @@ class EmojiPicker extends React.Component<IProps, IState> {
this.setState({ filter });
// Header underlines need to be updated, but updating requires knowing
// where the categories are, so we wait for a tick.
setTimeout(this.updateVisibility, 0);
window.setTimeout(this.updateVisibility, 0);
};
private emojiMatchesFilter = (emoji: IEmoji, filter: string): boolean => {

View file

@ -31,8 +31,8 @@ class Search extends React.PureComponent<IProps> {
private inputRef = React.createRef<HTMLInputElement>();
componentDidMount() {
// For some reason, neither the autoFocus nor just calling focus() here worked, so here's a setTimeout
setTimeout(() => this.inputRef.current.focus(), 0);
// For some reason, neither the autoFocus nor just calling focus() here worked, so here's a window.setTimeout
window.setTimeout(() => this.inputRef.current.focus(), 0);
}
private onKeyDown = (ev: React.KeyboardEvent) => {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React, { createRef } from 'react';
import filesize from 'filesize';
import { filesize } from 'filesize';
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../../languageHandler';

View file

@ -270,6 +270,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// Set a placeholder image when we can't decrypt the image.
this.setState({ error });
return;
}
} else {
thumbUrl = this.getThumbUrl();
@ -291,16 +292,27 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
img.crossOrigin = "Anonymous"; // CORS allow canvas access
img.src = contentUrl;
await loadPromise;
const blob = await this.props.mediaEventHelper.sourceBlob.value;
if (!await blobIsAnimated(content.info.mimetype, blob)) {
isAnimated = false;
try {
await loadPromise;
} catch (error) {
logger.error("Unable to download attachment: ", error);
this.setState({ error: error as Error });
return;
}
if (isAnimated) {
const thumb = await createThumbnail(img, img.width, img.height, content.info.mimetype, false);
thumbUrl = URL.createObjectURL(thumb.thumbnail);
try {
const blob = await this.props.mediaEventHelper.sourceBlob.value;
if (!await blobIsAnimated(content.info?.mimetype, blob)) {
isAnimated = false;
}
if (isAnimated) {
const thumb = await createThumbnail(img, img.width, img.height, content.info!.mimetype, false);
thumbUrl = URL.createObjectURL(thumb.thumbnail);
}
} catch (error) {
// This is a non-critical failure, do not surface the error or bail the method here
logger.warn("Unable to generate thumbnail for animated image: ", error);
}
}
}
@ -335,7 +347,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// Add a 150ms timer for blurhash to first appear.
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) {
this.clearBlurhashTimeout();
this.timeout = setTimeout(() => {
this.timeout = window.setTimeout(() => {
if (!this.state.imgLoaded || !this.state.imgError) {
this.setState({
placeholder: Placeholder.Blurhash,

View file

@ -130,7 +130,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
if (codes.length > 0) {
// Do this asynchronously: parsing code takes time and we don't
// need to block the DOM update on it.
setTimeout(() => {
window.setTimeout(() => {
if (this.unmounted) return;
for (let i = 0; i < codes.length; i++) {
this.highlightCode(codes[i]);

View file

@ -111,8 +111,21 @@ const EncryptionPanel: React.FC<IProps> = (props: IProps) => {
const onStartVerification = useCallback(async () => {
setRequesting(true);
const cli = MatrixClientPeg.get();
const roomId = await ensureDMExists(cli, member.userId);
const verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
let verificationRequest_: VerificationRequest;
try {
const roomId = await ensureDMExists(cli, member.userId);
verificationRequest_ = await cli.requestVerificationDM(member.userId, roomId);
} catch (e) {
console.error("Error starting verification", e);
setRequesting(false);
Modal.createDialog(ErrorDialog, {
headerImage: require("../../../../res/img/e2e/warning.svg").default,
title: _t("Error starting verification"),
description: _t("We were unable to start a chat with the other user."),
});
return;
}
setRequest(verificationRequest_);
setPhase(verificationRequest_.phase);
// Notify the RightPanelStore about this

View file

@ -127,7 +127,7 @@ export default class Autocomplete extends React.PureComponent<IProps, IState> {
}
return new Promise((resolve) => {
this.debounceCompletionsRequest = setTimeout(() => {
this.debounceCompletionsRequest = window.setTimeout(() => {
resolve(this.processQuery(query, selection));
}, autocompleteDelay);
});

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import React, { ComponentProps, createRef } from 'react';
import { AllHtmlEntities } from 'html-entities';
import { decode } from 'html-entities';
import { MatrixEvent } from 'matrix-js-sdk/src/models/event';
import { IPreviewUrlResponse } from 'matrix-js-sdk/src/client';
@ -124,7 +124,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
// The description includes &-encoded HTML entities, we decode those as React treats the thing as an
// opaque string. This does not allow any HTML to be injected into the DOM.
const description = AllHtmlEntities.decode(p["og:description"] || "");
const description = decode(p["og:description"] || "");
const title = p["og:title"]?.trim() ?? "";
const anchor = <a href={this.props.link} target="_blank" rel="noreferrer noopener">{ title }</a>;

View file

@ -199,7 +199,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
// that the ScrollPanel listening to the resizeNotifier can
// correctly measure it's new height and scroll down to keep
// at the bottom if it already is
setTimeout(() => {
window.setTimeout(() => {
this.props.resizeNotifier.notifyTimelineHeightChanged();
}, 100);
}
@ -395,7 +395,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
private onRecordingEndingSoon = ({ secondsLeft }) => {
this.setState({ recordingTimeLeftSeconds: secondsLeft });
setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000);
window.setTimeout(() => this.setState({ recordingTimeLeftSeconds: null }), 3000);
};
private setStickerPickerOpen = (isStickerPickerOpen: boolean) => {
@ -584,6 +584,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
setUpVoiceBroadcastPreRecording(
this.props.room,
MatrixClientPeg.get(),
SdkContextClass.instance.voiceBroadcastPlaybacksStore,
VoiceBroadcastRecordingsStore.instance(),
SdkContextClass.instance.voiceBroadcastPreRecordingStore,
);

View file

@ -123,6 +123,7 @@ export default class ReplyTile extends React.PureComponent<IProps> {
}
const classes = classNames("mx_ReplyTile", {
mx_ReplyTile_inline: msgType === MsgType.Emote,
mx_ReplyTile_info: isInfoMessage && !mxEvent.isRedacted(),
mx_ReplyTile_audio: msgType === MsgType.Audio,
mx_ReplyTile_video: msgType === MsgType.Video,

View file

@ -99,7 +99,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
// again and this time we want to show the newest breadcrumb because it'll be hidden
// off screen for the animation.
this.setState({ doAnimation: false, skipFirst: true });
setTimeout(() => this.setState({ doAnimation: true, skipFirst: false }), 0);
window.setTimeout(() => this.setState({ doAnimation: true, skipFirst: false }), 0);
};
private viewRoom = (room: Room, index: number, viaKeyboard = false) => {

View file

@ -385,7 +385,7 @@ const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
"mx_RoomHeader_layoutButton--spotlight": layout === Layout.Spotlight,
})}
onClick={onClick}
title={_t("Layout type")}
title={_t("Change layout")}
alignment={Alignment.Bottom}
key="layout"
/>

View file

@ -747,13 +747,12 @@ export default class RoomSublist extends React.Component<IProps, IState> {
public render(): React.ReactElement {
const visibleTiles = this.renderVisibleTiles();
const hidden = !this.state.rooms.length && !this.props.extraTiles?.length && this.props.alwaysVisible !== true;
const classes = classNames({
'mx_RoomSublist': true,
'mx_RoomSublist_hasMenuOpen': !!this.state.contextMenuPosition,
'mx_RoomSublist_minimized': this.props.isMinimized,
'mx_RoomSublist_hidden': (
!this.state.rooms.length && !this.props.extraTiles?.length && this.props.alwaysVisible !== true
),
'mx_RoomSublist_hidden': hidden,
});
let content = null;
@ -898,6 +897,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
ref={this.sublistRef}
className={classes}
role="group"
aria-hidden={hidden}
aria-label={this.props.label}
onKeyDown={this.onKeyDown}
>

View file

@ -28,7 +28,7 @@ export function useIsFocused() {
} else {
// To avoid a blink when we switch mode between plain text and rich text mode
// We delay the unfocused action
timeoutIDRef.current = setTimeout(() => setIsFocused(false), 100);
timeoutIDRef.current = window.setTimeout(() => setIsFocused(false), 100);
}
}, [setIsFocused, timeoutIDRef]);

View file

@ -37,7 +37,7 @@ export function focusComposer(
if (timeoutId.current) {
clearTimeout(timeoutId.current);
}
timeoutId.current = setTimeout(
timeoutId.current = window.setTimeout(
() => composerElement.current?.focus(),
200,
);

View file

@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { logger } from "matrix-js-sdk/src/logger";
@ -66,11 +65,6 @@ interface IBridgeStateEvent {
}
export default class BridgeTile extends React.PureComponent<IProps> {
static propTypes = {
ev: PropTypes.object.isRequired,
room: PropTypes.object.isRequired,
};
render() {
const content: IBridgeStateEvent = this.props.ev.getContent();
// Validate

View file

@ -150,7 +150,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } });
this.themeTimer = setTimeout(() => {
this.themeTimer = window.setTimeout(() => {
this.setState({ customThemeMessage: { text: "", isError: false } });
}, 3000);
};

View file

@ -64,12 +64,13 @@ const isDeviceSelected = (
) => selectedDeviceIds.includes(deviceId);
// devices without timestamp metadata should be sorted last
const sortDevicesByLatestActivity = (left: ExtendedDevice, right: ExtendedDevice) =>
(right.last_seen_ts || 0) - (left.last_seen_ts || 0);
const sortDevicesByLatestActivityThenDisplayName = (left: ExtendedDevice, right: ExtendedDevice) =>
(right.last_seen_ts || 0) - (left.last_seen_ts || 0)
|| ((left.display_name || left.device_id).localeCompare(right.display_name || right.device_id));
const getFilteredSortedDevices = (devices: DevicesDictionary, filter?: DeviceSecurityVariation) =>
filterDevicesBySecurityRecommendation(Object.values(devices), filter ? [filter] : [])
.sort(sortDevicesByLatestActivity);
.sort(sortDevicesByLatestActivityThenDisplayName);
const ALL_FILTER_ID = 'ALL';
type DeviceFilterKey = DeviceSecurityVariation | typeof ALL_FILTER_ID;

View file

@ -324,12 +324,13 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
let privilegedUsersSection = <div>{ _t('No users have specific privileges in this room') }</div>;
let mutedUsersSection;
if (Object.keys(userLevels).length) {
const privilegedUsers = [];
const mutedUsers = [];
const privilegedUsers: JSX.Element[] = [];
const mutedUsers: JSX.Element[] = [];
Object.keys(userLevels).forEach((user) => {
if (!Number.isInteger(userLevels[user])) { return; }
const canChange = userLevels[user] < currentUserLevel && canChangeLevels;
if (!Number.isInteger(userLevels[user])) return;
const isMe = user === client.getUserId();
const canChange = canChangeLevels && (userLevels[user] < currentUserLevel || isMe);
if (userLevels[user] > defaultUserLevel) { // privileged
privilegedUsers.push(
<PowerSelector

View file

@ -19,7 +19,6 @@ import { sortBy } from "lodash";
import { _t } from "../../../../../languageHandler";
import SettingsStore from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import { SettingLevel } from "../../../../../settings/SettingLevel";
import SdkConfig from "../../../../../SdkConfig";
import BetaCard from "../../../beta/BetaCard";
@ -28,24 +27,6 @@ import { MatrixClientPeg } from '../../../../../MatrixClientPeg';
import { LabGroup, labGroupNames } from "../../../../../settings/Settings";
import { EnhancedMap } from "../../../../../utils/maps";
interface ILabsSettingToggleProps {
featureId: string;
}
export class LabsSettingToggle extends React.Component<ILabsSettingToggleProps> {
private onChange = async (checked: boolean): Promise<void> => {
await SettingsStore.setValue(this.props.featureId, null, SettingLevel.DEVICE, checked);
this.forceUpdate();
};
public render(): JSX.Element {
const label = SettingsStore.getDisplayName(this.props.featureId);
const value = SettingsStore.getValue(this.props.featureId);
const canChange = SettingsStore.canSetValue(this.props.featureId, null, SettingLevel.DEVICE);
return <LabelledToggleSwitch value={value} label={label} onChange={this.onChange} disabled={!canChange} />;
}
}
interface IState {
showJumpToDate: boolean;
showExploringPublicSpaces: boolean;
@ -93,7 +74,7 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
const groups = new EnhancedMap<LabGroup, JSX.Element[]>();
labs.forEach(f => {
groups.getOrCreate(SettingsStore.getLabGroup(f), []).push(
<LabsSettingToggle featureId={f} key={f} />,
<SettingsFlag level={SettingLevel.DEVICE} name={f} key={f} />,
);
});
@ -154,24 +135,42 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
return (
<div className="mx_SettingsTab mx_LabsUserSettingsTab">
<div className="mx_SettingsTab_heading">{ _t("Labs") }</div>
<div className="mx_SettingsTab_heading">{ _t("Upcoming features") }</div>
<div className='mx_SettingsTab_subsectionText'>
{
_t('Feeling experimental? Labs are the best way to get things early, ' +
'test out new features and help shape them before they actually launch. ' +
'<a>Learn more</a>.', {}, {
'a': (sub) => {
return <a
href="https://github.com/vector-im/element-web/blob/develop/docs/labs.md"
rel='noreferrer noopener'
target='_blank'
>{ sub }</a>;
},
})
_t(
"What's next for %(brand)s? "
+ "Labs are the best way to get things early, "
+ "test out new features and help shape them before they actually launch.",
{ brand: SdkConfig.get("brand") },
)
}
</div>
{ betaSection }
{ labsSections }
{ labsSections && <>
<div className="mx_SettingsTab_heading">{ _t("Early previews") }</div>
<div className='mx_SettingsTab_subsectionText'>
{
_t(
"Feeling experimental? "
+ "Try out our latest ideas in development. "
+ "These features are not finalised; "
+ "they may be unstable, may change, or may be dropped altogether. "
+ "<a>Learn more</a>.",
{},
{
'a': (sub) => {
return <a
href="https://github.com/vector-im/element-web/blob/develop/docs/labs.md"
rel='noreferrer noopener'
target='_blank'
>{ sub }</a>;
},
})
}
</div>
{ labsSections }
</> }
</div>
);
}

View file

@ -127,7 +127,7 @@ const SessionManagerTab: React.FC = () => {
const [expandedDeviceIds, setExpandedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
const [selectedDeviceIds, setSelectedDeviceIds] = useState<ExtendedDevice['device_id'][]>([]);
const filteredDeviceListRef = useRef<HTMLDivElement>(null);
const scrollIntoViewTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
const scrollIntoViewTimeoutRef = useRef<number>();
const matrixClient = useContext(MatrixClientContext);
const userId = matrixClient.getUserId();

View file

@ -57,7 +57,7 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
async componentDidMount() {
const { request } = this.props;
if (request.timeout && request.timeout > 0) {
this.intervalHandle = setInterval(() => {
this.intervalHandle = window.setInterval(() => {
let { counter } = this.state;
counter = Math.max(0, counter - 1);
this.setState({ counter });

View file

@ -55,7 +55,7 @@ export function UserOnboardingPage({ justRegistered = false }: Props) {
const [showList, setShowList] = useState<boolean>(false);
useEffect(() => {
if (initialSyncComplete) {
let handler: number | null = setTimeout(() => {
let handler: number | null = window.setTimeout(() => {
handler = null;
setShowList(true);
}, ANIMATION_DURATION);

View file

@ -43,7 +43,7 @@ interface GroupCallDurationProps {
export const GroupCallDuration: FC<GroupCallDurationProps> = ({ groupCall }) => {
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
const timer = setInterval(() => setNow(Date.now()), 1000);
const timer = window.setInterval(() => setNow(Date.now()), 1000);
return () => clearInterval(timer);
}, []);

View file

@ -367,14 +367,14 @@ class PipView extends React.Component<IProps, IState> {
const pipMode = true;
let pipContent: CreatePipChildren | null = null;
if (this.props.voiceBroadcastPreRecording) {
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
}
if (this.props.voiceBroadcastPlayback) {
pipContent = this.createVoiceBroadcastPlaybackPipContent(this.props.voiceBroadcastPlayback);
}
if (this.props.voiceBroadcastPreRecording) {
pipContent = this.createVoiceBroadcastPreRecordingPipContent(this.props.voiceBroadcastPreRecording);
}
if (this.props.voiceBroadcastRecording) {
pipContent = this.createVoiceBroadcastRecordingPipContent(this.props.voiceBroadcastRecording);
}