Merge branch 'develop' into warn-on-access-token-reveal

This commit is contained in:
Aaron Raimist 2021-04-27 19:25:38 -05:00
commit 31ff05a862
103 changed files with 3943 additions and 1715 deletions

View file

@ -1,5 +1,5 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -28,10 +28,17 @@ import {SettingLevel} from "../../../settings/SettingLevel";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import SeshatResetDialog from '../dialogs/SeshatResetDialog';
interface IState {
enabling: boolean;
eventIndexSize: number;
roomCount: number;
eventIndexingEnabled: boolean;
}
@replaceableComponent("views.settings.EventIndexPanel")
export default class EventIndexPanel extends React.Component {
constructor() {
super();
export default class EventIndexPanel extends React.Component<{}, IState> {
constructor(props) {
super(props);
this.state = {
enabling: false,
@ -68,7 +75,7 @@ export default class EventIndexPanel extends React.Component {
}
}
async componentDidMount(): void {
componentDidMount(): void {
this.updateState();
}
@ -102,8 +109,10 @@ export default class EventIndexPanel extends React.Component {
});
}
_onManage = async () => {
private onManage = async () => {
Modal.createTrackedDialogAsync('Message search', 'Message search',
// @ts-ignore: TS doesn't seem to like the type of this now that it
// has also been converted to TS as well, but I can't figure out why...
import('../../../async-components/views/dialogs/eventindex/ManageEventIndexDialog'),
{
onFinished: () => {},
@ -111,7 +120,7 @@ export default class EventIndexPanel extends React.Component {
);
}
_onEnable = async () => {
private onEnable = async () => {
this.setState({
enabling: true,
});
@ -123,14 +132,13 @@ export default class EventIndexPanel extends React.Component {
await this.updateState();
}
_confirmEventStoreReset = () => {
const self = this;
private confirmEventStoreReset = () => {
const { close } = Modal.createDialog(SeshatResetDialog, {
onFinished: async (success) => {
if (success) {
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, false);
await EventIndexPeg.deleteEventIndex();
await self._onEnable();
await this.onEnable();
close();
}
},
@ -145,20 +153,19 @@ export default class EventIndexPanel extends React.Component {
if (EventIndexPeg.get() !== null) {
eventIndexingSettings = (
<div>
<div className='mx_SettingsTab_subsectionText'>
{_t("Securely cache encrypted messages locally for them " +
"to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
{
size: formatBytes(this.state.eventIndexSize, 0),
// This drives the singular / plural string
// selection for "room" / "rooms" only.
count: this.state.roomCount,
rooms: formatCountLong(this.state.roomCount),
},
)}
</div>
<div className='mx_SettingsTab_subsectionText'>{_t(
"Securely cache encrypted messages locally for them " +
"to appear in search results, using %(size)s to store messages from %(rooms)s rooms.",
{
size: formatBytes(this.state.eventIndexSize, 0),
// This drives the singular / plural string
// selection for "room" / "rooms" only.
count: this.state.roomCount,
rooms: formatCountLong(this.state.roomCount),
},
)}</div>
<div>
<AccessibleButton kind="primary" onClick={this._onManage}>
<AccessibleButton kind="primary" onClick={this.onManage}>
{_t("Manage")}
</AccessibleButton>
</div>
@ -167,13 +174,13 @@ export default class EventIndexPanel extends React.Component {
} else if (!this.state.eventIndexingEnabled && EventIndexPeg.supportIsInstalled()) {
eventIndexingSettings = (
<div>
<div className='mx_SettingsTab_subsectionText'>
{_t( "Securely cache encrypted messages locally for them to " +
"appear in search results.")}
</div>
<div className='mx_SettingsTab_subsectionText'>{_t(
"Securely cache encrypted messages locally for them to " +
"appear in search results.",
)}</div>
<div>
<AccessibleButton kind="primary" disabled={this.state.enabling}
onClick={this._onEnable}>
onClick={this.onEnable}>
{_t("Enable")}
</AccessibleButton>
{this.state.enabling ? <InlineSpinner /> : <div />}
@ -188,40 +195,36 @@ export default class EventIndexPanel extends React.Component {
);
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>
<div className='mx_SettingsTab_subsectionText'>{_t(
"%(brand)s is missing some components required for securely " +
"caching encrypted messages locally. If you'd like to " +
"experiment with this feature, build a custom %(brand)s Desktop " +
"with <nativeLink>search components added</nativeLink>.",
{
_t( "%(brand)s is missing some components required for securely " +
"caching encrypted messages locally. If you'd like to " +
"experiment with this feature, build a custom %(brand)s Desktop " +
"with <nativeLink>search components added</nativeLink>.",
{
brand,
},
{
'nativeLink': (sub) => <a href={nativeLink} target="_blank"
rel="noreferrer noopener">{sub}</a>,
},
)
}
</div>
brand,
},
{
nativeLink: sub => <a href={nativeLink}
target="_blank" rel="noreferrer noopener"
>{sub}</a>,
},
)}</div>
);
} else if (!EventIndexPeg.platformHasSupport()) {
eventIndexingSettings = (
<div className='mx_SettingsTab_subsectionText'>
<div className='mx_SettingsTab_subsectionText'>{_t(
"%(brand)s can't securely cache encrypted messages locally " +
"while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> " +
"for encrypted messages to appear in search results.",
{
_t( "%(brand)s can't securely cache encrypted messages locally " +
"while running in a web browser. Use <desktopLink>%(brand)s Desktop</desktopLink> " +
"for encrypted messages to appear in search results.",
{
brand,
},
{
'desktopLink': (sub) => <a href="https://element.io/get-started"
target="_blank" rel="noreferrer noopener">{sub}</a>,
},
)
}
</div>
brand,
},
{
desktopLink: sub => <a href="https://element.io/get-started"
target="_blank" rel="noreferrer noopener"
>{sub}</a>,
},
)}</div>
);
} else {
eventIndexingSettings = (
@ -233,19 +236,18 @@ export default class EventIndexPanel extends React.Component {
}
</p>
{EventIndexPeg.error && (
<details>
<summary>{_t("Advanced")}</summary>
<code>
{EventIndexPeg.error.message}
</code>
<p>
<AccessibleButton key="delete" kind="danger" onClick={this._confirmEventStoreReset}>
{_t("Reset")}
</AccessibleButton>
</p>
</details>
<details>
<summary>{_t("Advanced")}</summary>
<code>
{EventIndexPeg.error.message}
</code>
<p>
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
{_t("Reset")}
</AccessibleButton>
</p>
</details>
)}
</div>
);
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,7 +16,6 @@ limitations under the License.
import url from 'url';
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import * as sdk from '../../../index';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
@ -28,6 +27,7 @@ import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../../utils/IdentityServerUtils';
import {timeout} from "../../../utils/promise";
import {replaceableComponent} from "../../../utils/replaceableComponent";
import { ActionPayload } from '../../../dispatcher/payloads';
// We'll wait up to this long when checking for 3PID bindings on the IS.
const REACHABILITY_TIMEOUT = 10000; // ms
@ -59,16 +59,28 @@ async function checkIdentityServerUrl(u) {
}
}
@replaceableComponent("views.settings.SetIdServer")
export default class SetIdServer extends React.Component {
static propTypes = {
// Whether or not the ID server is missing terms. This affects the text
// shown to the user.
missingTerms: PropTypes.bool,
};
interface IProps {
// Whether or not the ID server is missing terms. This affects the text
// shown to the user.
missingTerms: boolean;
}
constructor() {
super();
interface IState {
defaultIdServer?: string;
currentClientIdServer: string;
idServer?: string;
error?: string;
busy: boolean;
disconnectBusy: boolean;
checking: boolean;
}
@replaceableComponent("views.settings.SetIdServer")
export default class SetIdServer extends React.Component<IProps, IState> {
private dispatcherRef: string;
constructor(props) {
super(props);
let defaultIdServer = '';
if (!MatrixClientPeg.get().getIdentityServerUrl() && getDefaultIdentityServerUrl()) {
@ -96,7 +108,7 @@ export default class SetIdServer extends React.Component {
dis.unregister(this.dispatcherRef);
}
onAction = (payload) => {
private onAction = (payload: ActionPayload) => {
// We react to changes in the ID server in the event the user is staring at this form
// when changing their identity server on another device.
if (payload.action !== "id_server_changed") return;
@ -106,13 +118,13 @@ export default class SetIdServer extends React.Component {
});
};
_onIdentityServerChanged = (ev) => {
private onIdentityServerChanged = (ev) => {
const u = ev.target.value;
this.setState({idServer: u});
};
_getTooltip = () => {
private getTooltip = () => {
if (this.state.checking) {
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
return <div>
@ -126,11 +138,11 @@ export default class SetIdServer extends React.Component {
}
};
_idServerChangeEnabled = () => {
private idServerChangeEnabled = () => {
return !!this.state.idServer && !this.state.busy;
};
_saveIdServer = (fullUrl) => {
private saveIdServer = (fullUrl) => {
// Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", {
base_url: fullUrl,
@ -143,7 +155,7 @@ export default class SetIdServer extends React.Component {
});
};
_checkIdServer = async (e) => {
private checkIdServer = async (e) => {
e.preventDefault();
const { idServer, currentClientIdServer } = this.state;
@ -166,14 +178,14 @@ export default class SetIdServer extends React.Component {
// Double check that the identity server even has terms of service.
const hasTerms = await doesIdentityServerHaveTerms(fullUrl);
if (!hasTerms) {
const [confirmed] = await this._showNoTermsWarning(fullUrl);
const [confirmed] = await this.showNoTermsWarning(fullUrl);
save = confirmed;
}
// Show a general warning, possibly with details about any bound
// 3PIDs that would be left behind.
if (save && currentClientIdServer && fullUrl !== currentClientIdServer) {
const [confirmed] = await this._showServerChangeWarning({
const [confirmed] = await this.showServerChangeWarning({
title: _t("Change identity server"),
unboundMessage: _t(
"Disconnect from the identity server <current /> and " +
@ -189,7 +201,7 @@ export default class SetIdServer extends React.Component {
}
if (save) {
this._saveIdServer(fullUrl);
this.saveIdServer(fullUrl);
}
} catch (e) {
console.error(e);
@ -204,7 +216,7 @@ export default class SetIdServer extends React.Component {
});
};
_showNoTermsWarning(fullUrl) {
private showNoTermsWarning(fullUrl) {
const QuestionDialog = sdk.getComponent("views.dialogs.QuestionDialog");
const { finished } = Modal.createTrackedDialog('No Terms Warning', '', QuestionDialog, {
title: _t("Identity server has no terms of service"),
@ -223,10 +235,10 @@ export default class SetIdServer extends React.Component {
return finished;
}
_onDisconnectClicked = async () => {
private onDisconnectClicked = async () => {
this.setState({disconnectBusy: true});
try {
const [confirmed] = await this._showServerChangeWarning({
const [confirmed] = await this.showServerChangeWarning({
title: _t("Disconnect identity server"),
unboundMessage: _t(
"Disconnect from the identity server <idserver />?", {},
@ -235,14 +247,14 @@ export default class SetIdServer extends React.Component {
button: _t("Disconnect"),
});
if (confirmed) {
this._disconnectIdServer();
this.disconnectIdServer();
}
} finally {
this.setState({disconnectBusy: false});
}
};
async _showServerChangeWarning({ title, unboundMessage, button }) {
private async showServerChangeWarning({ title, unboundMessage, button }) {
const { currentClientIdServer } = this.state;
let threepids = [];
@ -318,7 +330,7 @@ export default class SetIdServer extends React.Component {
return finished;
}
_disconnectIdServer = () => {
private disconnectIdServer = () => {
// Account data change will update localstorage, client, etc through dispatcher
MatrixClientPeg.get().setAccountData("m.identity_server", {
base_url: null, // clear
@ -371,7 +383,7 @@ export default class SetIdServer extends React.Component {
let discoSection;
if (idServerUrl) {
let discoButtonContent = _t("Disconnect");
let discoButtonContent: React.ReactNode = _t("Disconnect");
let discoBodyText = _t(
"Disconnecting from your identity server will mean you " +
"won't be discoverable by other users and you won't be " +
@ -391,14 +403,14 @@ export default class SetIdServer extends React.Component {
}
discoSection = <div>
<span className="mx_SettingsTab_subsectionText">{discoBodyText}</span>
<AccessibleButton onClick={this._onDisconnectClicked} kind="danger_sm">
<AccessibleButton onClick={this.onDisconnectClicked} kind="danger_sm">
{discoButtonContent}
</AccessibleButton>
</div>;
}
return (
<form className="mx_SettingsTab_section mx_SetIdServer" onSubmit={this._checkIdServer}>
<form className="mx_SettingsTab_section mx_SetIdServer" onSubmit={this.checkIdServer}>
<span className="mx_SettingsTab_subheading">
{sectionTitle}
</span>
@ -411,15 +423,15 @@ export default class SetIdServer extends React.Component {
autoComplete="off"
placeholder={this.state.defaultIdServer}
value={this.state.idServer}
onChange={this._onIdentityServerChanged}
tooltipContent={this._getTooltip()}
onChange={this.onIdentityServerChanged}
tooltipContent={this.getTooltip()}
tooltipClassName="mx_SetIdServer_tooltip"
disabled={this.state.busy}
forceValidity={this.state.error ? false : null}
/>
<AccessibleButton type="submit" kind="primary_sm"
onClick={this._checkIdServer}
disabled={!this._idServerChangeEnabled()}
onClick={this.checkIdServer}
disabled={!this.idServerChangeEnabled()}
>{_t("Change")}</AccessibleButton>
{discoSection}
</form>

View file

@ -1,5 +1,5 @@
/*
Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t, _td} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
@ -23,6 +22,9 @@ import AccessibleButton from "../../../elements/AccessibleButton";
import Modal from "../../../../../Modal";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import {EventType} from "matrix-js-sdk/src/@types/event";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
const plEventsToLabels = {
// These will be translated for us later.
@ -63,15 +65,15 @@ function parseIntWithDefault(val, def) {
return isNaN(res) ? def : res;
}
export class BannedUser extends React.Component {
static propTypes = {
canUnban: PropTypes.bool,
member: PropTypes.object.isRequired, // js-sdk RoomMember
by: PropTypes.string.isRequired,
reason: PropTypes.string,
};
interface IBannedUserProps {
canUnban?: boolean;
member: RoomMember;
by: string;
reason?: string;
}
_onUnbanClick = (e) => {
export class BannedUser extends React.Component<IBannedUserProps> {
private onUnbanClick = (e) => {
MatrixClientPeg.get().unban(this.props.member.roomId, this.props.member.userId).catch((err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to unban: " + err);
@ -87,8 +89,10 @@ export class BannedUser extends React.Component {
if (this.props.canUnban) {
unbanButton = (
<AccessibleButton kind='danger_sm' onClick={this._onUnbanClick}
className='mx_RolesRoomSettingsTab_unbanBtn'>
<AccessibleButton className='mx_RolesRoomSettingsTab_unbanBtn'
kind='danger_sm'
onClick={this.onUnbanClick}
>
{ _t('Unban') }
</AccessibleButton>
);
@ -107,29 +111,29 @@ export class BannedUser extends React.Component {
}
}
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
};
interface IProps {
roomId: string;
}
componentDidMount(): void {
MatrixClientPeg.get().on("RoomState.members", this._onRoomMembership);
@replaceableComponent("views.settings.tabs.room.RolesRoomSettingsTab")
export default class RolesRoomSettingsTab extends React.Component<IProps> {
componentDidMount() {
MatrixClientPeg.get().on("RoomState.members", this.onRoomMembership);
}
componentWillUnmount(): void {
componentWillUnmount() {
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("RoomState.members", this._onRoomMembership);
client.removeListener("RoomState.members", this.onRoomMembership);
}
}
_onRoomMembership = (event, state, member) => {
private onRoomMembership = (event: MatrixEvent, state: RoomState, member: RoomMember) => {
if (state.roomId !== this.props.roomId) return;
this.forceUpdate();
};
_populateDefaultPlEvents(eventsSection, stateLevel, eventsLevel) {
private populateDefaultPlEvents(eventsSection: Record<string, number>, stateLevel: number, eventsLevel: number) {
for (const desiredEvent of Object.keys(plEventsToShow)) {
if (!(desiredEvent in eventsSection)) {
eventsSection[desiredEvent] = (plEventsToShow[desiredEvent].isState ? stateLevel : eventsLevel);
@ -137,7 +141,7 @@ export default class RolesRoomSettingsTab extends React.Component {
}
}
_onPowerLevelsChanged = (value, powerLevelKey) => {
private onPowerLevelsChanged = (inputValue: string, powerLevelKey: string) => {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
@ -148,7 +152,7 @@ export default class RolesRoomSettingsTab extends React.Component {
const eventsLevelPrefix = "event_levels_";
value = parseInt(value);
const value = parseInt(inputValue);
if (powerLevelKey.startsWith(eventsLevelPrefix)) {
// deep copy "events" object, Object.assign itself won't deep copy
@ -182,7 +186,7 @@ export default class RolesRoomSettingsTab extends React.Component {
});
};
_onUserPowerLevelChanged = (value, powerLevelKey) => {
private onUserPowerLevelChanged = (value: string, powerLevelKey: string) => {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
@ -266,7 +270,7 @@ export default class RolesRoomSettingsTab extends React.Component {
currentUserLevel = defaultUserLevel;
}
this._populateDefaultPlEvents(
this.populateDefaultPlEvents(
eventsLevels,
parseIntWithDefault(plContent.state_default, powerLevelDescriptors.state_default.defaultValue),
parseIntWithDefault(plContent.events_default, powerLevelDescriptors.events_default.defaultValue),
@ -288,7 +292,7 @@ export default class RolesRoomSettingsTab extends React.Component {
label={user}
key={user}
powerLevelKey={user} // Will be sent as the second parameter to `onChange`
onChange={this._onUserPowerLevelChanged}
onChange={this.onUserPowerLevelChanged}
/>,
);
} else if (userLevels[user] < defaultUserLevel) { // muted
@ -299,7 +303,7 @@ export default class RolesRoomSettingsTab extends React.Component {
label={user}
key={user}
powerLevelKey={user} // Will be sent as the second parameter to `onChange`
onChange={this._onUserPowerLevelChanged}
onChange={this.onUserPowerLevelChanged}
/>,
);
}
@ -345,8 +349,9 @@ export default class RolesRoomSettingsTab extends React.Component {
if (sender) bannedBy = sender.name;
return (
<BannedUser key={member.userId} canUnban={canBanUsers}
member={member} reason={banEvent.reason}
by={bannedBy} />
member={member} reason={banEvent.reason}
by={bannedBy}
/>
);
})}
</ul>
@ -373,7 +378,7 @@ export default class RolesRoomSettingsTab extends React.Component {
usersDefault={defaultUserLevel}
disabled={!canChangeLevels || currentUserLevel < value}
powerLevelKey={key} // Will be sent as the second parameter to `onChange`
onChange={this._onPowerLevelsChanged}
onChange={this.onPowerLevelsChanged}
/>
</div>;
});
@ -398,7 +403,7 @@ export default class RolesRoomSettingsTab extends React.Component {
usersDefault={defaultUserLevel}
disabled={!canChangeLevels || currentUserLevel < eventsLevels[eventType]}
powerLevelKey={"event_levels_" + eventType}
onChange={this._onPowerLevelsChanged}
onChange={this.onPowerLevelsChanged}
/>
</div>
);

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,7 +15,7 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
@ -26,64 +26,92 @@ import StyledRadioGroup from '../../../elements/StyledRadioGroup';
import {SettingLevel} from "../../../../../settings/SettingLevel";
import SettingsStore from "../../../../../settings/SettingsStore";
import {UIFeature} from "../../../../../settings/UIFeature";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
// Knock and private are reserved keywords which are not yet implemented.
enum JoinRule {
Public = "public",
Knock = "knock",
Invite = "invite",
Private = "private",
}
enum GuestAccess {
CanJoin = "can_join",
Forbidden = "forbidden",
}
enum HistoryVisibility {
Invited = "invited",
Joined = "joined",
Shared = "shared",
WorldReadable = "world_readable",
}
interface IProps {
roomId: string;
}
interface IState {
joinRule: JoinRule;
guestAccess: GuestAccess;
history: HistoryVisibility;
hasAliases: boolean;
encrypted: boolean;
}
@replaceableComponent("views.settings.tabs.room.SecurityRoomSettingsTab")
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
};
constructor() {
super();
export default class SecurityRoomSettingsTab extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
joinRule: "invite",
guestAccess: "can_join",
history: "shared",
joinRule: JoinRule.Invite,
guestAccess: GuestAccess.CanJoin,
history: HistoryVisibility.Shared,
hasAliases: false,
encrypted: false,
};
}
// TODO: [REACT-WARNING] Move this to constructor
async UNSAFE_componentWillMount(): void { // eslint-disable-line camelcase
MatrixClientPeg.get().on("RoomState.events", this._onStateEvent);
async UNSAFE_componentWillMount() { // eslint-disable-line camelcase
MatrixClientPeg.get().on("RoomState.events", this.onStateEvent);
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
const state = room.currentState;
const joinRule = this._pullContentPropertyFromEvent(
const joinRule: JoinRule = this.pullContentPropertyFromEvent(
state.getStateEvents("m.room.join_rules", ""),
'join_rule',
'invite',
JoinRule.Invite,
);
const guestAccess = this._pullContentPropertyFromEvent(
const guestAccess: GuestAccess = this.pullContentPropertyFromEvent(
state.getStateEvents("m.room.guest_access", ""),
'guest_access',
'forbidden',
GuestAccess.Forbidden,
);
const history = this._pullContentPropertyFromEvent(
const history: HistoryVisibility = this.pullContentPropertyFromEvent(
state.getStateEvents("m.room.history_visibility", ""),
'history_visibility',
'shared',
HistoryVisibility.Shared,
);
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
this.setState({joinRule, guestAccess, history, encrypted});
const hasAliases = await this._hasAliases();
const hasAliases = await this.hasAliases();
this.setState({hasAliases});
}
_pullContentPropertyFromEvent(event, key, defaultValue) {
private pullContentPropertyFromEvent<T>(event: MatrixEvent, key: string, defaultValue: T): T {
if (!event || !event.getContent()) return defaultValue;
return event.getContent()[key] || defaultValue;
}
componentWillUnmount(): void {
MatrixClientPeg.get().removeListener("RoomState.events", this._onStateEvent);
componentWillUnmount() {
MatrixClientPeg.get().removeListener("RoomState.events", this.onStateEvent);
}
_onStateEvent = (e) => {
private onStateEvent = (e: MatrixEvent) => {
const refreshWhenTypes = [
'm.room.join_rules',
'm.room.guest_access',
@ -93,7 +121,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
if (refreshWhenTypes.includes(e.getType())) this.forceUpdate();
};
_onEncryptionChange = (e) => {
private onEncryptionChange = (e: React.ChangeEvent) => {
Modal.createTrackedDialog('Enable encryption', '', QuestionDialog, {
title: _t('Enable encryption?'),
description: _t(
@ -102,10 +130,9 @@ export default class SecurityRoomSettingsTab extends React.Component {
"may prevent many bots and bridges from working correctly. <a>Learn more about encryption.</a>",
{},
{
'a': (sub) => {
return <a rel='noreferrer noopener' target='_blank'
href='https://element.io/help#encryption'>{sub}</a>;
},
a: sub => <a href="https://element.io/help#encryption"
rel="noreferrer noopener" target="_blank"
>{sub}</a>,
},
),
onFinished: (confirm) => {
@ -127,12 +154,12 @@ export default class SecurityRoomSettingsTab extends React.Component {
});
};
_fixGuestAccess = (e) => {
private fixGuestAccess = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const joinRule = "invite";
const guestAccess = "can_join";
const joinRule = JoinRule.Invite;
const guestAccess = GuestAccess.CanJoin;
const beforeJoinRule = this.state.joinRule;
const beforeGuestAccess = this.state.guestAccess;
@ -149,7 +176,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
});
};
_onRoomAccessRadioToggle = (roomAccess) => {
private onRoomAccessRadioToggle = (roomAccess: string) => {
// join_rule
// INVITE | PUBLIC
// ----------------------+----------------
@ -163,20 +190,20 @@ export default class SecurityRoomSettingsTab extends React.Component {
// invite them, you clearly want them to join, whether they're a
// guest or not. In practice, guest_access should probably have
// been implemented as part of the join_rules enum.
let joinRule = "invite";
let guestAccess = "can_join";
let joinRule = JoinRule.Invite;
let guestAccess = GuestAccess.CanJoin;
switch (roomAccess) {
case "invite_only":
// no change - use defaults above
break;
case "public_no_guests":
joinRule = "public";
guestAccess = "forbidden";
joinRule = JoinRule.Public;
guestAccess = GuestAccess.Forbidden;
break;
case "public_with_guests":
joinRule = "public";
guestAccess = "can_join";
joinRule = JoinRule.Public;
guestAccess = GuestAccess.CanJoin;
break;
}
@ -195,7 +222,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
});
};
_onHistoryRadioToggle = (history) => {
private onHistoryRadioToggle = (history: HistoryVisibility) => {
const beforeHistory = this.state.history;
this.setState({history: history});
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
@ -206,11 +233,11 @@ export default class SecurityRoomSettingsTab extends React.Component {
});
};
_updateBlacklistDevicesFlag = (checked) => {
private updateBlacklistDevicesFlag = (checked: boolean) => {
MatrixClientPeg.get().getRoom(this.props.roomId).setBlacklistUnverifiedDevices(checked);
};
async _hasAliases() {
private async hasAliases(): Promise<boolean> {
const cli = MatrixClientPeg.get();
if (await cli.doesServerSupportUnstableFeature("org.matrix.msc2432")) {
const response = await cli.unstableGetLocalAliases(this.props.roomId);
@ -224,7 +251,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
}
}
_renderRoomAccess() {
private renderRoomAccess() {
const client = MatrixClientPeg.get();
const room = client.getRoom(this.props.roomId);
const joinRule = this.state.joinRule;
@ -240,7 +267,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
<img src={require("../../../../../../res/img/warning.svg")} width={15} height={15} />
<span>
{_t("Guests cannot join this room even if explicitly invited.")}&nbsp;
<a href="" onClick={this._fixGuestAccess}>{_t("Click here to fix")}</a>
<a href="" onClick={this.fixGuestAccess}>{_t("Click here to fix")}</a>
</span>
</div>
);
@ -265,7 +292,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
<StyledRadioGroup
name="roomVis"
value={joinRule}
onChange={this._onRoomAccessRadioToggle}
onChange={this.onRoomAccessRadioToggle}
definitions={[
{
value: "invite_only",
@ -291,7 +318,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
);
}
_renderHistory() {
private renderHistory() {
const client = MatrixClientPeg.get();
const history = this.state.history;
const state = client.getRoom(this.props.roomId).currentState;
@ -306,25 +333,25 @@ export default class SecurityRoomSettingsTab extends React.Component {
<StyledRadioGroup
name="historyVis"
value={history}
onChange={this._onHistoryRadioToggle}
onChange={this.onHistoryRadioToggle}
definitions={[
{
value: "world_readable",
value: HistoryVisibility.WorldReadable,
disabled: !canChangeHistory,
label: _t("Anyone"),
},
{
value: "shared",
value: HistoryVisibility.Shared,
disabled: !canChangeHistory,
label: _t('Members only (since the point in time of selecting this option)'),
},
{
value: "invited",
value: HistoryVisibility.Invited,
disabled: !canChangeHistory,
label: _t('Members only (since they were invited)'),
},
{
value: "joined",
value: HistoryVisibility.Joined,
disabled: !canChangeHistory,
label: _t('Members only (since they joined)'),
},
@ -348,7 +375,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
encryptionSettings = <SettingsFlag
name="blacklistUnverifiedDevices"
level={SettingLevel.ROOM_DEVICE}
onChange={this._updateBlacklistDevicesFlag}
onChange={this.updateBlacklistDevicesFlag}
roomId={this.props.roomId}
/>;
}
@ -356,7 +383,7 @@ export default class SecurityRoomSettingsTab extends React.Component {
let historySection = (<>
<span className='mx_SettingsTab_subheading'>{_t("Who can read history?")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
{this._renderHistory()}
{this.renderHistory()}
</div>
</>);
if (!SettingsStore.getValue(UIFeature.RoomHistorySettings)) {
@ -373,15 +400,16 @@ export default class SecurityRoomSettingsTab extends React.Component {
<div className='mx_SettingsTab_subsectionText'>
<span>{_t("Once enabled, encryption cannot be disabled.")}</span>
</div>
<LabelledToggleSwitch value={isEncrypted} onChange={this._onEncryptionChange}
label={_t("Encrypted")} disabled={!canEnableEncryption} />
<LabelledToggleSwitch value={isEncrypted} onChange={this.onEncryptionChange}
label={_t("Encrypted")} disabled={!canEnableEncryption}
/>
</div>
{encryptionSettings}
</div>
<span className='mx_SettingsTab_subheading'>{_t("Who can access this room?")}</span>
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
{this._renderRoomAccess()}
{this.renderRoomAccess()}
</div>
{historySection}

View file

@ -192,7 +192,11 @@ export default class GeneralUserSettingsTab extends React.Component {
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage);
this.setState({language: newLanguage});
PlatformPeg.get().reload();
const platform = PlatformPeg.get();
if (platform) {
platform.setLanguage(newLanguage);
platform.reload();
}
};
_onSpellCheckLanguagesChange = (languages) => {

View file

@ -1,6 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -16,7 +15,6 @@ limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import {_t, getCurrentLanguage} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
@ -24,24 +22,28 @@ import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton';
import SdkConfig from "../../../../../SdkConfig";
import createRoom from "../../../../../createRoom";
import Modal from "../../../../../Modal";
import * as sdk from "../../../../../";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
import UpdateCheckButton from "../../UpdateCheckButton";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import {copyPlaintext} from "../../../../../utils/strings";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { copyPlaintext } from "../../../../../utils/strings";
import * as ContextMenu from "../../../../structures/ContextMenu";
import {toRightOf} from "../../../../structures/ContextMenu";
import { toRightOf } from "../../../../structures/ContextMenu";
interface IProps {
closeSettingsFn: () => {};
}
interface IState {
appVersion: string;
canUpdate: boolean;
}
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
export default class HelpUserSettingsTab extends React.Component {
static propTypes = {
closeSettingsFn: PropTypes.func.isRequired,
};
constructor() {
super();
export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = {
appVersion: null,
@ -58,7 +60,7 @@ export default class HelpUserSettingsTab extends React.Component {
});
}
_onClearCacheAndReload = (e) => {
private onClearCacheAndReload = (e) => {
if (!PlatformPeg.get()) return;
// Dev note: please keep this log line, it's useful when troubleshooting a MatrixClient suddenly
@ -70,7 +72,7 @@ export default class HelpUserSettingsTab extends React.Component {
});
};
_onBugReport = (e) => {
private onBugReport = (e) => {
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
if (!BugReportDialog) {
return;
@ -78,7 +80,7 @@ export default class HelpUserSettingsTab extends React.Component {
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
};
_onStartBotChat = (e) => {
private onStartBotChat = (e) => {
this.props.closeSettingsFn();
createRoom({
dmUserId: SdkConfig.get().welcomeUserId,
@ -86,7 +88,7 @@ export default class HelpUserSettingsTab extends React.Component {
});
};
_showSpoiler = (event) => {
private showSpoiler = (event) => {
const target = event.target;
target.innerHTML = target.getAttribute('data-spoiler');
@ -98,7 +100,7 @@ export default class HelpUserSettingsTab extends React.Component {
selection.addRange(range);
};
_renderLegal() {
private renderLegal() {
const tocLinks = SdkConfig.get().terms_and_conditions_links;
if (!tocLinks) return null;
@ -119,7 +121,7 @@ export default class HelpUserSettingsTab extends React.Component {
);
}
_renderCredits() {
private renderCredits() {
// Note: This is not translated because it is legal text.
// Also, &nbsp; is ugly but necessary.
return (
@ -127,28 +129,28 @@ export default class HelpUserSettingsTab extends React.Component {
<span className='mx_SettingsTab_subheading'>{_t("Credits")}</span>
<ul>
<li>
The <a href="themes/element/img/backgrounds/lake.jpg" rel="noreferrer noopener" target="_blank">
default cover photo</a> is ©&nbsp;
<a href="https://www.flickr.com/golan" rel="noreferrer noopener" target="_blank">Jesús Roncero</a>{' '}
used under the terms of&nbsp;
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noreferrer noopener" target="_blank">
CC-BY-SA 4.0</a>.
The <a href="themes/element/img/backgrounds/lake.jpg" rel="noreferrer noopener"
target="_blank">default cover photo</a> is ©&nbsp;
<a href="https://www.flickr.com/golan" rel="noreferrer noopener"
target="_blank">Jesús Roncero</a> used under the terms of&nbsp;
<a href="https://creativecommons.org/licenses/by-sa/4.0/" rel="noreferrer noopener"
target="_blank">CC-BY-SA 4.0</a>.
</li>
<li>
The <a href="https://github.com/matrix-org/twemoji-colr" rel="noreferrer noopener"
target="_blank"> twemoji-colr</a> font is ©&nbsp;
<a href="https://mozilla.org" rel="noreferrer noopener" target="_blank">Mozilla Foundation</a>{' '}
used under the terms of&nbsp;
<a href="http://www.apache.org/licenses/LICENSE-2.0" rel="noreferrer noopener" target="_blank">
Apache 2.0</a>.
target="_blank">twemoji-colr</a> font is ©&nbsp;
<a href="https://mozilla.org" rel="noreferrer noopener"
target="_blank">Mozilla Foundation</a> used under the terms of&nbsp;
<a href="http://www.apache.org/licenses/LICENSE-2.0" rel="noreferrer noopener"
target="_blank">Apache 2.0</a>.
</li>
<li>
The <a href="https://twemoji.twitter.com/" rel="noreferrer noopener" target="_blank">
Twemoji</a> emoji art is ©&nbsp;
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener" target="_blank">Twitter, Inc and other
contributors</a> used under the terms of&nbsp;
<a href="https://creativecommons.org/licenses/by/4.0/" rel="noreferrer noopener" target="_blank">
CC-BY 4.0</a>.
The <a href="https://twemoji.twitter.com/" rel="noreferrer noopener"
target="_blank">Twemoji</a> emoji art is ©&nbsp;
<a href="https://twemoji.twitter.com/" rel="noreferrer noopener"
target="_blank">Twitter, Inc and other contributors</a> used under the terms of&nbsp;
<a href="https://creativecommons.org/licenses/by/4.0/" rel="noreferrer noopener"
target="_blank">CC-BY 4.0</a>.
</li>
</ul>
</div>
@ -207,7 +209,7 @@ export default class HelpUserSettingsTab extends React.Component {
},
)}
<div>
<AccessibleButton onClick={this._onStartBotChat} kind='primary'>
<AccessibleButton onClick={this.onStartBotChat} kind='primary'>
{_t("Chat with %(brand)s Bot", { brand })}
</AccessibleButton>
</div>
@ -231,28 +233,27 @@ export default class HelpUserSettingsTab extends React.Component {
<div className="mx_SettingsTab_section">
<span className='mx_SettingsTab_subheading'>{_t('Bug reporting')}</span>
<div className='mx_SettingsTab_subsectionText'>
{
_t( "If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
)
}
{_t(
"If you've submitted a bug via GitHub, debug logs can help " +
"us track down the problem. Debug logs contain application " +
"usage data including your username, the IDs or aliases of " +
"the rooms or groups you have visited and the usernames of " +
"other users. They do not contain messages.",
)}
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this._onBugReport} kind='primary'>
<AccessibleButton onClick={this.onBugReport} kind='primary'>
{_t("Submit debug logs")}
</AccessibleButton>
</div>
{
_t( "To report a Matrix-related security issue, please read the Matrix.org " +
"<a>Security Disclosure Policy</a>.", {},
{
'a': (sub) =>
<a href="https://matrix.org/security-disclosure-policy/"
rel="noreferrer noopener" target="_blank">{sub}</a>,
})
}
{_t(
"To report a Matrix-related security issue, please read the Matrix.org " +
"<a>Security Disclosure Policy</a>.", {},
{
a: sub => <a href="https://matrix.org/security-disclosure-policy/"
rel="noreferrer noopener" target="_blank"
>{sub}</a>,
},
)}
</div>
</div>
);
@ -279,8 +280,8 @@ export default class HelpUserSettingsTab extends React.Component {
{updateButton}
</div>
</div>
{this._renderLegal()}
{this._renderCredits()}
{this.renderLegal()}
{this.renderCredits()}
<div className='mx_SettingsTab_section mx_HelpUserSettingsTab_versions'>
<span className='mx_SettingsTab_subheading'>{_t("Advanced")}</span>
<div className='mx_SettingsTab_subsectionText'>
@ -301,7 +302,7 @@ export default class HelpUserSettingsTab extends React.Component {
</div>
</details><br />
<div className='mx_HelpUserSettingsTab_debugButton'>
<AccessibleButton onClick={this._onClearCacheAndReload} kind='danger'>
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
{_t("Clear cache and reload")}
</AccessibleButton>
</div>

View file

@ -1,5 +1,5 @@
/*
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -25,10 +25,16 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
interface IState {
busy: boolean;
newPersonalRule: string;
newList: string;
}
@replaceableComponent("views.settings.tabs.user.MjolnirUserSettingsTab")
export default class MjolnirUserSettingsTab extends React.Component {
constructor() {
super();
export default class MjolnirUserSettingsTab extends React.Component<{}, IState> {
constructor(props) {
super(props);
this.state = {
busy: false,
@ -37,15 +43,15 @@ export default class MjolnirUserSettingsTab extends React.Component {
};
}
_onPersonalRuleChanged = (e) => {
private onPersonalRuleChanged = (e) => {
this.setState({newPersonalRule: e.target.value});
};
_onNewListChanged = (e) => {
private onNewListChanged = (e) => {
this.setState({newList: e.target.value});
};
_onAddPersonalRule = async (e) => {
private onAddPersonalRule = async (e) => {
e.preventDefault();
e.stopPropagation();
@ -72,7 +78,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
};
_onSubscribeList = async (e) => {
private onSubscribeList = async (e) => {
e.preventDefault();
e.stopPropagation();
@ -94,7 +100,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
};
async _removePersonalRule(rule: ListRule) {
private async removePersonalRule(rule: ListRule) {
this.setState({busy: true});
try {
const list = Mjolnir.sharedInstance().getPersonalList();
@ -112,7 +118,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
}
async _unsubscribeFromList(list: BanList) {
private async unsubscribeFromList(list: BanList) {
this.setState({busy: true});
try {
await Mjolnir.sharedInstance().unsubscribeFromList(list.roomId);
@ -130,7 +136,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
}
_viewListRules(list: BanList) {
private viewListRules(list: BanList) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const room = MatrixClientPeg.get().getRoom(list.roomId);
@ -161,7 +167,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
});
}
_renderPersonalBanListRules() {
private renderPersonalBanListRules() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const list = Mjolnir.sharedInstance().getPersonalList();
@ -174,7 +180,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
<li key={rule.entity} className="mx_MjolnirUserSettingsTab_listItem">
<AccessibleButton
kind="danger_sm"
onClick={() => this._removePersonalRule(rule)}
onClick={() => this.removePersonalRule(rule)}
disabled={this.state.busy}
>
{_t("Remove")}
@ -192,7 +198,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
);
}
_renderSubscribedBanLists() {
private renderSubscribedBanLists() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const personalList = Mjolnir.sharedInstance().getPersonalList();
@ -209,14 +215,14 @@ export default class MjolnirUserSettingsTab extends React.Component {
<li key={list.roomId} className="mx_MjolnirUserSettingsTab_listItem">
<AccessibleButton
kind="danger_sm"
onClick={() => this._unsubscribeFromList(list)}
onClick={() => this.unsubscribeFromList(list)}
disabled={this.state.busy}
>
{_t("Unsubscribe")}
</AccessibleButton>&nbsp;
<AccessibleButton
kind="primary_sm"
onClick={() => this._viewListRules(list)}
onClick={() => this.viewListRules(list)}
disabled={this.state.busy}
>
{_t("View rules")}
@ -271,21 +277,21 @@ export default class MjolnirUserSettingsTab extends React.Component {
)}
</div>
<div>
{this._renderPersonalBanListRules()}
{this.renderPersonalBanListRules()}
</div>
<div>
<form onSubmit={this._onAddPersonalRule} autoComplete="off">
<form onSubmit={this.onAddPersonalRule} autoComplete="off">
<Field
type="text"
label={_t("Server or user ID to ignore")}
placeholder={_t("eg: @bot:* or example.org")}
value={this.state.newPersonalRule}
onChange={this._onPersonalRuleChanged}
onChange={this.onPersonalRuleChanged}
/>
<AccessibleButton
type="submit"
kind="primary"
onClick={this._onAddPersonalRule}
onClick={this.onAddPersonalRule}
disabled={this.state.busy}
>
{_t("Ignore")}
@ -303,20 +309,20 @@ export default class MjolnirUserSettingsTab extends React.Component {
)}</span>
</div>
<div>
{this._renderSubscribedBanLists()}
{this.renderSubscribedBanLists()}
</div>
<div>
<form onSubmit={this._onSubscribeList} autoComplete="off">
<form onSubmit={this.onSubscribeList} autoComplete="off">
<Field
type="text"
label={_t("Room ID or address of ban list")}
value={this.state.newList}
onChange={this._onNewListChanged}
onChange={this.onNewListChanged}
/>
<AccessibleButton
type="submit"
kind="primary"
onClick={this._onSubscribeList}
onClick={this.onSubscribeList}
disabled={this.state.busy}
>
{_t("Subscribe")}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
@ -23,10 +23,24 @@ import Field from "../../../elements/Field";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel";
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
interface IState {
autoLaunch: boolean;
autoLaunchSupported: boolean;
warnBeforeExit: boolean;
warnBeforeExitSupported: boolean;
alwaysShowMenuBarSupported: boolean;
alwaysShowMenuBar: boolean;
minimizeToTraySupported: boolean;
minimizeToTray: boolean;
autocompleteDelay: string;
readMarkerInViewThresholdMs: string;
readMarkerOutOfViewThresholdMs: string;
}
@replaceableComponent("views.settings.tabs.user.PreferencesUserSettingsTab")
export default class PreferencesUserSettingsTab extends React.Component {
export default class PreferencesUserSettingsTab extends React.Component<{}, IState> {
static ROOM_LIST_SETTINGS = [
'breadcrumbs',
];
@ -68,8 +82,8 @@ export default class PreferencesUserSettingsTab extends React.Component {
// Autocomplete delay (niche text box)
];
constructor() {
super();
constructor(props) {
super(props);
this.state = {
autoLaunch: false,
@ -89,7 +103,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
};
}
async componentDidMount(): void {
async componentDidMount() {
const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch();
@ -128,38 +142,38 @@ export default class PreferencesUserSettingsTab extends React.Component {
});
}
_onAutoLaunchChange = (checked) => {
private onAutoLaunchChange = (checked: boolean) => {
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
};
_onWarnBeforeExitChange = (checked) => {
private onWarnBeforeExitChange = (checked: boolean) => {
PlatformPeg.get().setWarnBeforeExit(checked).then(() => this.setState({warnBeforeExit: checked}));
}
_onAlwaysShowMenuBarChange = (checked) => {
private onAlwaysShowMenuBarChange = (checked: boolean) => {
PlatformPeg.get().setAutoHideMenuBarEnabled(!checked).then(() => this.setState({alwaysShowMenuBar: checked}));
};
_onMinimizeToTrayChange = (checked) => {
private onMinimizeToTrayChange = (checked: boolean) => {
PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked}));
};
_onAutocompleteDelayChange = (e) => {
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({autocompleteDelay: e.target.value});
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
};
_onReadMarkerInViewThresholdMs = (e) => {
private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({readMarkerInViewThresholdMs: e.target.value});
SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
};
_onReadMarkerOutOfViewThresholdMs = (e) => {
private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({readMarkerOutOfViewThresholdMs: e.target.value});
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
};
_renderGroup(settingIds) {
private renderGroup(settingIds: string[]): React.ReactNodeArray {
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
return settingIds.filter(SettingsStore.isEnabled).map(i => {
return <SettingsFlag key={i} name={i} level={SettingLevel.ACCOUNT} />;
@ -171,7 +185,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
if (this.state.autoLaunchSupported) {
autoLaunchOption = <LabelledToggleSwitch
value={this.state.autoLaunch}
onChange={this._onAutoLaunchChange}
onChange={this.onAutoLaunchChange}
label={_t('Start automatically after system login')} />;
}
@ -179,7 +193,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
if (this.state.warnBeforeExitSupported) {
warnBeforeExitOption = <LabelledToggleSwitch
value={this.state.warnBeforeExit}
onChange={this._onWarnBeforeExitChange}
onChange={this.onWarnBeforeExitChange}
label={_t('Warn before quitting')} />;
}
@ -187,7 +201,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
if (this.state.alwaysShowMenuBarSupported) {
autoHideMenuOption = <LabelledToggleSwitch
value={this.state.alwaysShowMenuBar}
onChange={this._onAlwaysShowMenuBarChange}
onChange={this.onAlwaysShowMenuBarChange}
label={_t('Always show the window menu bar')} />;
}
@ -195,7 +209,7 @@ export default class PreferencesUserSettingsTab extends React.Component {
if (this.state.minimizeToTraySupported) {
minimizeToTrayOption = <LabelledToggleSwitch
value={this.state.minimizeToTray}
onChange={this._onMinimizeToTrayChange}
onChange={this.onMinimizeToTrayChange}
label={_t('Show tray icon and minimize window to it on close')} />;
}
@ -205,22 +219,22 @@ export default class PreferencesUserSettingsTab extends React.Component {
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Room list")}</span>
{this._renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
{this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
{this._renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
{this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("Timeline")}</span>
{this._renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
{this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
</div>
<div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{_t("General")}</span>
{this._renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}
{this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}
{minimizeToTrayOption}
{autoHideMenuOption}
{autoLaunchOption}
@ -229,17 +243,17 @@ export default class PreferencesUserSettingsTab extends React.Component {
label={_t('Autocomplete delay (ms)')}
type='number'
value={this.state.autocompleteDelay}
onChange={this._onAutocompleteDelayChange} />
onChange={this.onAutocompleteDelayChange} />
<Field
label={_t('Read Marker lifetime (ms)')}
type='number'
value={this.state.readMarkerInViewThresholdMs}
onChange={this._onReadMarkerInViewThresholdMs} />
onChange={this.onReadMarkerInViewThresholdMs} />
<Field
label={_t('Read Marker off-screen lifetime (ms)')}
type='number'
value={this.state.readMarkerOutOfViewThresholdMs}
onChange={this._onReadMarkerOutOfViewThresholdMs} />
onChange={this.onReadMarkerOutOfViewThresholdMs} />
</div>
</div>
);