Merge remote-tracking branch 'origin/develop' into feat/emoji-picker-rich-text-mode
This commit is contained in:
commit
54e12d265b
139 changed files with 2830 additions and 3202 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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}
|
||||
>
|
||||
|
|
|
@ -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]);
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ export function focusComposer(
|
|||
if (timeoutId.current) {
|
||||
clearTimeout(timeoutId.current);
|
||||
}
|
||||
timeoutId.current = setTimeout(
|
||||
timeoutId.current = window.setTimeout(
|
||||
() => composerElement.current?.focus(),
|
||||
200,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue