Merge branch 'develop' into e2ee-history-visibility
This commit is contained in:
commit
d568e92e06
921 changed files with 32884 additions and 19078 deletions
|
@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useState} from "react";
|
||||
import React, { useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import classNames from "classnames";
|
||||
|
||||
const AvatarSetting = ({avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar}) => {
|
||||
const AvatarSetting = ({ avatarUrl, avatarAltText, avatarName, uploadAvatar, removeAvatar }) => {
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const hoveringProps = {
|
||||
onMouseEnter: () => setIsHovering(true),
|
||||
|
|
|
@ -16,16 +16,16 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import Pill from "../elements/Pill";
|
||||
import {makeUserPermalink} from "../../../utils/permalinks/Permalinks";
|
||||
import { makeUserPermalink } from "../../../utils/permalinks/Permalinks";
|
||||
import BaseAvatar from "../avatars/BaseAvatar";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { isUrlPermitted } from '../../../HtmlUtils';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../../customisations/Media";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
|
||||
interface IProps {
|
||||
ev: MatrixEvent;
|
||||
|
@ -69,7 +69,7 @@ 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();
|
||||
|
@ -131,7 +131,9 @@ export default class BridgeTile extends React.PureComponent<IProps> {
|
|||
const networkName = network.displayname || network.id;
|
||||
let networkLink = <span>{networkName}</span>;
|
||||
if (typeof network.external_url === "string" && isUrlPermitted(network.external_url)) {
|
||||
networkLink = <a href={network.external_url} target="_blank" rel="noreferrer noopener">{networkName}</a>
|
||||
networkLink = (
|
||||
<a href={network.external_url} target="_blank" rel="noreferrer noopener">{networkName}</a>
|
||||
);
|
||||
}
|
||||
networkItem = _t("Workspace: <networkLink/>", {}, {
|
||||
networkLink: () => networkLink,
|
||||
|
@ -140,7 +142,7 @@ export default class BridgeTile extends React.PureComponent<IProps> {
|
|||
|
||||
let channelLink = <span>{channelName}</span>;
|
||||
if (typeof channel.external_url === "string" && isUrlPermitted(channel.external_url)) {
|
||||
channelLink = <a href={channel.external_url} target="_blank" rel="noreferrer noopener">{channelName}</a>
|
||||
channelLink = <a href={channel.external_url} target="_blank" rel="noreferrer noopener">{channelName}</a>;
|
||||
}
|
||||
|
||||
const id = this.props.ev.getId();
|
||||
|
|
|
@ -16,12 +16,12 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../../customisations/Media";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
|
||||
@replaceableComponent("views.settings.ChangeAvatar")
|
||||
export default class ChangeAvatar extends React.Component {
|
||||
|
@ -107,7 +107,7 @@ export default class ChangeAvatar extends React.Component {
|
|||
return MatrixClientPeg.get().sendStateEvent(
|
||||
self.props.room.roomId,
|
||||
'm.room.avatar',
|
||||
{url: url},
|
||||
{ url: url },
|
||||
'',
|
||||
);
|
||||
} else {
|
||||
|
@ -154,7 +154,7 @@ export default class ChangeAvatar extends React.Component {
|
|||
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
|
||||
// XXX: FIXME: once we track in the JS what our own displayname is(!) then use it here rather than ?
|
||||
avatarImg = <BaseAvatar width={this.props.width} height={this.props.height} resizeMethod='crop'
|
||||
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
|
||||
name='?' idName={MatrixClientPeg.get().getUserIdLocalpart()} url={this.state.avatarUrl} />;
|
||||
}
|
||||
|
||||
let uploadSection;
|
||||
|
|
|
@ -18,9 +18,9 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import * as sdk from '../../../index';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.ChangeDisplayName")
|
||||
export default class ChangeDisplayName extends React.Component {
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
import Field from "../elements/Field";
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import withValidation from '../elements/Validation';
|
||||
|
@ -27,7 +27,7 @@ import * as sdk from "../../../index";
|
|||
import Modal from "../../../Modal";
|
||||
import PassphraseField from "../auth/PassphraseField";
|
||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { PASSWORD_MIN_SCORE } from '../auth/RegistrationForm';
|
||||
|
||||
const FIELD_OLD_PASSWORD = 'field_old_password';
|
||||
|
@ -206,7 +206,7 @@ export default class ChangePassword extends React.Component {
|
|||
test: ({ value, allowEmpty }) => allowEmpty || !!value,
|
||||
invalid: () => _t("Passwords can't be empty"),
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
onChangeNewPassword = (ev) => {
|
||||
|
@ -245,7 +245,7 @@ export default class ChangePassword extends React.Component {
|
|||
},
|
||||
invalid: () => _t("Passwords don't match"),
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
onClickChange = async (ev) => {
|
||||
|
|
|
@ -16,14 +16,14 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import Spinner from '../elements/Spinner';
|
||||
import InteractiveAuthDialog from '../dialogs/InteractiveAuthDialog';
|
||||
import ConfirmDestroyCrossSigningDialog from '../dialogs/security/ConfirmDestroyCrossSigningDialog';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.CrossSigningPanel")
|
||||
export default class CrossSigningPanel extends React.PureComponent {
|
||||
|
@ -79,8 +79,8 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
async _getUpdatedStatus() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const pkCache = cli.getCrossSigningCacheCallbacks();
|
||||
const crossSigning = cli._crypto._crossSigningInfo;
|
||||
const secretStorage = cli._crypto._secretStorage;
|
||||
const crossSigning = cli.crypto.crossSigningInfo;
|
||||
const secretStorage = cli.crypto.secretStorage;
|
||||
const crossSigningPublicKeysOnDevice = crossSigning.getId();
|
||||
const crossSigningPrivateKeysInStorage = await crossSigning.isStoredInSecretStorage(secretStorage);
|
||||
const masterPrivateKeyCached = !!(pkCache && await pkCache.getCrossSigningKeyCache("master"));
|
||||
|
@ -259,7 +259,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
|||
<td>{_t("Homeserver feature support:")}</td>
|
||||
<td>{homeserverSupportsCrossSigning ? _t("exists") : _t("not found")}</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</tbody></table>
|
||||
</details>
|
||||
{errorSection}
|
||||
{actionRow}
|
||||
|
|
|
@ -20,11 +20,11 @@ import PropTypes from 'prop-types';
|
|||
import classNames from 'classnames';
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import {SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.DevicesPanel")
|
||||
export default class DevicesPanel extends React.Component {
|
||||
|
@ -58,7 +58,7 @@ export default class DevicesPanel extends React.Component {
|
|||
MatrixClientPeg.get().getDevices().then(
|
||||
(resp) => {
|
||||
if (this._unmounted) { return; }
|
||||
this.setState({devices: resp.devices || []});
|
||||
this.setState({ devices: resp.devices || [] });
|
||||
},
|
||||
(error) => {
|
||||
if (this._unmounted) { return; }
|
||||
|
@ -70,12 +70,11 @@ export default class DevicesPanel extends React.Component {
|
|||
console.error("Error loading sessions:", error);
|
||||
errtxt = _t("Unable to load session list");
|
||||
}
|
||||
this.setState({deviceLoadError: errtxt});
|
||||
this.setState({ deviceLoadError: errtxt });
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* compare two devices, sorting from most-recently-seen to least-recently-seen
|
||||
* (and then, for stability, by device id)
|
||||
|
@ -107,7 +106,7 @@ export default class DevicesPanel extends React.Component {
|
|||
selectedDevices.splice(i, 1);
|
||||
}
|
||||
|
||||
return {selectedDevices};
|
||||
return { selectedDevices };
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -141,7 +140,7 @@ export default class DevicesPanel extends React.Component {
|
|||
body: _t("Click the button below to confirm deleting these sessions.", {
|
||||
count: numDevices,
|
||||
}),
|
||||
continueText: _t("Delete sessions", {count: numDevices}),
|
||||
continueText: _t("Delete sessions", { count: numDevices }),
|
||||
continueKind: "danger",
|
||||
},
|
||||
};
|
||||
|
@ -214,7 +213,7 @@ export default class DevicesPanel extends React.Component {
|
|||
const deleteButton = this.state.deleting ?
|
||||
<Spinner w={22} h={22} /> :
|
||||
<AccessibleButton onClick={this._onDeleteClick} kind="danger_sm">
|
||||
{ _t("Delete %(count)s sessions", {count: this.state.selectedDevices.length}) }
|
||||
{ _t("Delete %(count)s sessions", { count: this.state.selectedDevices.length })}
|
||||
</AccessibleButton>;
|
||||
|
||||
const classes = classNames(this.props.className, "mx_DevicesPanel");
|
||||
|
|
|
@ -19,10 +19,10 @@ import PropTypes from 'prop-types';
|
|||
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import {formatDate} from '../../../DateUtils';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { formatDate } from '../../../DateUtils';
|
||||
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.DevicesPanelEntry")
|
||||
export default class DevicesPanelEntry extends React.Component {
|
||||
|
|
|
@ -16,15 +16,14 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import * as sdk from '../../../index';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import SettingsFlag from '../elements/SettingsFlag';
|
||||
|
||||
const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";
|
||||
|
||||
const E2eAdvancedPanel = props => {
|
||||
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
|
||||
return <div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Encryption")}</span>
|
||||
|
||||
|
|
|
@ -18,15 +18,15 @@ import React from 'react';
|
|||
|
||||
import { _t } from '../../../languageHandler';
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import * as sdk from '../../../index';
|
||||
import Modal from '../../../Modal';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
|
||||
import { formatBytes, formatCountLong } from "../../../utils/FormattingUtils";
|
||||
import EventIndexPeg from "../../../indexing/EventIndexPeg";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import SeshatResetDialog from '../dialogs/SeshatResetDialog';
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
|
||||
interface IState {
|
||||
enabling: boolean;
|
||||
|
@ -118,7 +118,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
|||
onFinished: () => {},
|
||||
}, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
private onEnable = async () => {
|
||||
this.setState({
|
||||
|
@ -130,7 +130,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
|||
await EventIndexPeg.get().startCrawler();
|
||||
await SettingsStore.setValue('enableEventIndexing', null, SettingLevel.DEVICE, true);
|
||||
await this.updateState();
|
||||
}
|
||||
};
|
||||
|
||||
private confirmEventStoreReset = () => {
|
||||
const { close } = Modal.createDialog(SeshatResetDialog, {
|
||||
|
@ -143,11 +143,10 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
|||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
let eventIndexingSettings = null;
|
||||
const InlineSpinner = sdk.getComponent('elements.InlineSpinner');
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
if (EventIndexPeg.get() !== null) {
|
||||
|
@ -232,7 +231,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
|||
<p>
|
||||
{this.state.enabling
|
||||
? <InlineSpinner />
|
||||
: _t("Message search initilisation failed")
|
||||
: _t("Message search initialisation failed")
|
||||
}
|
||||
</p>
|
||||
{EventIndexPeg.error && (
|
||||
|
|
|
@ -20,8 +20,8 @@ import PropTypes from 'prop-types';
|
|||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import {Key} from "../../../Keyboard";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { Key } from "../../../Keyboard";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.IntegrationManager")
|
||||
export default class IntegrationManager extends React.Component {
|
||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import * as sdk from '../../../index';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import SettingsStore from '../../../settings/SettingsStore';
|
||||
import Modal from '../../../Modal';
|
||||
import {
|
||||
|
@ -30,9 +30,9 @@ import {
|
|||
import SdkConfig from "../../../SdkConfig";
|
||||
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
|
||||
import AccessibleButton from "../elements/AccessibleButton";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
// TODO: this "view" component still has far too much application logic in it,
|
||||
// which should be factored out to other files.
|
||||
|
@ -40,7 +40,6 @@ import {replaceableComponent} from "../../../utils/replaceableComponent";
|
|||
// TODO: this component also does a lot of direct poking into this.state, which
|
||||
// is VERY NAUGHTY.
|
||||
|
||||
|
||||
/**
|
||||
* Rules that Vector used to set in order to override the actions of default rules.
|
||||
* These are used to port peoples existing overrides to match the current API.
|
||||
|
@ -100,7 +99,7 @@ export default class Notifications extends React.Component {
|
|||
MatrixClientPeg.get().setPushRuleEnabled(
|
||||
'global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked,
|
||||
).then(function() {
|
||||
self._refreshFromServer();
|
||||
self._refreshFromServer();
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -517,7 +516,7 @@ export default class Notifications extends React.Component {
|
|||
};
|
||||
|
||||
// HS default rules
|
||||
const defaultRules = {master: [], vector: {}, others: []};
|
||||
const defaultRules = { master: [], vector: {}, others: [] };
|
||||
|
||||
for (const kind in rulesets.global) {
|
||||
for (let i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
|
||||
|
@ -580,12 +579,12 @@ export default class Notifications extends React.Component {
|
|||
"vectorRuleId": "_keywords",
|
||||
"description": (
|
||||
<span>
|
||||
{ _t('Messages containing <span>keywords</span>',
|
||||
{},
|
||||
{ 'span': (sub) =>
|
||||
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
|
||||
},
|
||||
)}
|
||||
{ _t('Messages containing <span>keywords</span>',
|
||||
{},
|
||||
{ 'span': (sub) =>
|
||||
<span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>{sub}</span>,
|
||||
},
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
"vectorState": self.state.vectorContentRules.vectorState,
|
||||
|
@ -632,7 +631,7 @@ export default class Notifications extends React.Component {
|
|||
});
|
||||
|
||||
const pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
|
||||
self.setState({pushers: resp.pushers});
|
||||
self.setState({ pushers: resp.pushers });
|
||||
});
|
||||
|
||||
Promise.all([pushRulesPromise, pushersPromise]).then(function() {
|
||||
|
@ -655,7 +654,7 @@ export default class Notifications extends React.Component {
|
|||
});
|
||||
});
|
||||
|
||||
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
|
||||
MatrixClientPeg.get().getThreePids().then((r) => this.setState({ threepids: r.threepids }));
|
||||
};
|
||||
|
||||
_onClearNotifications = () => {
|
||||
|
@ -743,8 +742,8 @@ export default class Notifications extends React.Component {
|
|||
|
||||
emailNotificationsRow(address, label) {
|
||||
return <LabelledToggleSwitch value={this.hasEmailPusher(this.state.pushers, address)}
|
||||
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
|
||||
label={label} key={`emailNotif_${label}`} />;
|
||||
onChange={this.onEnableEmailNotificationsChange.bind(this, address)}
|
||||
label={label} key={`emailNotif_${label}`} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -757,8 +756,8 @@ export default class Notifications extends React.Component {
|
|||
let masterPushRuleDiv;
|
||||
if (this.state.masterPushRule) {
|
||||
masterPushRuleDiv = <LabelledToggleSwitch value={!this.state.masterPushRule.enabled}
|
||||
onChange={this.onEnableNotificationsChange}
|
||||
label={_t('Enable notifications for this account')} />;
|
||||
onChange={this.onEnableNotificationsChange}
|
||||
label={_t('Enable notifications for this account')} />;
|
||||
}
|
||||
|
||||
let clearNotificationsButton;
|
||||
|
@ -874,16 +873,16 @@ export default class Notifications extends React.Component {
|
|||
{ spinner }
|
||||
|
||||
<LabelledToggleSwitch value={SettingsStore.getValue("notificationsEnabled")}
|
||||
onChange={this.onEnableDesktopNotificationsChange}
|
||||
label={_t('Enable desktop notifications for this session')} />
|
||||
onChange={this.onEnableDesktopNotificationsChange}
|
||||
label={_t('Enable desktop notifications for this session')} />
|
||||
|
||||
<LabelledToggleSwitch value={SettingsStore.getValue("notificationBodyEnabled")}
|
||||
onChange={this.onEnableDesktopNotificationBodyChange}
|
||||
label={_t('Show message in desktop notification')} />
|
||||
onChange={this.onEnableDesktopNotificationBodyChange}
|
||||
label={_t('Show message in desktop notification')} />
|
||||
|
||||
<LabelledToggleSwitch value={SettingsStore.getValue("audioNotificationsEnabled")}
|
||||
onChange={this.onEnableAudioNotificationsChange}
|
||||
label={_t('Enable audible notifications for this session')} />
|
||||
onChange={this.onEnableAudioNotificationsChange}
|
||||
label={_t('Enable audible notifications for this session')} />
|
||||
|
||||
{ emailNotificationsRows }
|
||||
|
||||
|
|
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import React, { createRef } from 'react';
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import Field from "../elements/Field";
|
||||
import { getHostingLink } from '../../../utils/HostingLink';
|
||||
import * as sdk from "../../../index";
|
||||
import {OwnProfileStore} from "../../../stores/OwnProfileStore";
|
||||
import { OwnProfileStore } from "../../../stores/OwnProfileStore";
|
||||
import Modal from "../../../Modal";
|
||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import {mediaFromMxc} from "../../../customisations/Media";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { mediaFromMxc } from "../../../customisations/Media";
|
||||
|
||||
@replaceableComponent("views.settings.ProfileSettings")
|
||||
export default class ProfileSettings extends React.Component {
|
||||
|
@ -79,7 +79,7 @@ export default class ProfileSettings extends React.Component {
|
|||
e.preventDefault();
|
||||
|
||||
if (!this.state.enableProfileSave) return;
|
||||
this.setState({enableProfileSave: false});
|
||||
this.setState({ enableProfileSave: false });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const newState = {};
|
||||
|
@ -170,8 +170,12 @@ export default class ProfileSettings extends React.Component {
|
|||
noValidate={true}
|
||||
className="mx_ProfileSettings_profileForm"
|
||||
>
|
||||
<input type="file" ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
onChange={this._onAvatarChanged} accept="image/*" />
|
||||
<input
|
||||
type="file"
|
||||
ref={this._avatarUpload} className="mx_ProfileSettings_avatarUpload"
|
||||
onChange={this._onAvatarChanged}
|
||||
accept="image/*"
|
||||
/>
|
||||
<div className="mx_ProfileSettings_profile">
|
||||
<div className="mx_ProfileSettings_controls">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Profile")}</span>
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import Modal from '../../../Modal';
|
||||
import { isSecureBackupRequired } from '../../../utils/WellKnownUtils';
|
||||
|
@ -26,7 +26,7 @@ import AccessibleButton from '../elements/AccessibleButton';
|
|||
import QuestionDialog from '../dialogs/QuestionDialog';
|
||||
import RestoreKeyBackupDialog from '../dialogs/security/RestoreKeyBackupDialog';
|
||||
import { accessSecretStorage } from '../../../SecurityManager';
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.SecureBackupPanel")
|
||||
export default class SecureBackupPanel extends React.PureComponent {
|
||||
|
@ -85,7 +85,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
|||
async _checkKeyBackupStatus() {
|
||||
this._getUpdatedDiagnostics();
|
||||
try {
|
||||
const {backupInfo, trustInfo} = await MatrixClientPeg.get().checkKeyBackup();
|
||||
const { backupInfo, trustInfo } = await MatrixClientPeg.get().checkKeyBackup();
|
||||
this.setState({
|
||||
loading: false,
|
||||
error: null,
|
||||
|
@ -131,10 +131,10 @@ export default class SecureBackupPanel extends React.PureComponent {
|
|||
|
||||
async _getUpdatedDiagnostics() {
|
||||
const cli = MatrixClientPeg.get();
|
||||
const secretStorage = cli._crypto._secretStorage;
|
||||
const secretStorage = cli.crypto.secretStorage;
|
||||
|
||||
const backupKeyStored = !!(await cli.isKeyBackupKeyStored());
|
||||
const backupKeyFromCache = await cli._crypto.getSessionBackupPrivateKey();
|
||||
const backupKeyFromCache = await cli.crypto.getSessionBackupPrivateKey();
|
||||
const backupKeyCached = !!(backupKeyFromCache);
|
||||
const backupKeyWellFormed = backupKeyFromCache instanceof Uint8Array;
|
||||
const secretStorageKeyInAccount = await secretStorage.hasKey();
|
||||
|
@ -172,7 +172,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
|||
danger: true,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
this.setState({loading: true});
|
||||
this.setState({ loading: true });
|
||||
MatrixClientPeg.get().deleteKeyBackupVersion(this.state.backupInfo.version).then(() => {
|
||||
this._loadBackupStatus();
|
||||
});
|
||||
|
@ -237,7 +237,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
|||
"This session is <b>not backing up your keys</b>, " +
|
||||
"but you do have an existing backup you can restore from " +
|
||||
"and add to going forward.", {},
|
||||
{b: sub => <b>{sub}</b>},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
)}</p>
|
||||
<p>{_t(
|
||||
"Connect this session to key backup before signing out to avoid " +
|
||||
|
@ -385,7 +385,7 @@ export default class SecureBackupPanel extends React.PureComponent {
|
|||
statusDescription = <>
|
||||
<p>{_t(
|
||||
"Your keys are <b>not being backed up from this session</b>.", {},
|
||||
{b: sub => <b>{sub}</b>},
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
)}</p>
|
||||
<p>{_t("Back up your keys before signing out to avoid losing them.")}</p>
|
||||
</>;
|
||||
|
|
|
@ -16,18 +16,21 @@ limitations under the License.
|
|||
|
||||
import url from 'url';
|
||||
import React from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import * as sdk from '../../../index';
|
||||
import {MatrixClientPeg} from "../../../MatrixClientPeg";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
import Modal from '../../../Modal';
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import { getThreepidsWithBindStatus } from '../../../boundThreepids';
|
||||
import IdentityAuthClient from "../../../IdentityAuthClient";
|
||||
import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
|
||||
import { abbreviateUrl, unabbreviateUrl } from "../../../utils/UrlUtils";
|
||||
import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../../utils/IdentityServerUtils';
|
||||
import {timeout} from "../../../utils/promise";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { timeout } from "../../../utils/promise";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
import { ActionPayload } from '../../../dispatcher/payloads';
|
||||
import InlineSpinner from '../elements/InlineSpinner';
|
||||
import AccessibleButton from '../elements/AccessibleButton';
|
||||
import Field from '../elements/Field';
|
||||
import QuestionDialog from "../dialogs/QuestionDialog";
|
||||
|
||||
// We'll wait up to this long when checking for 3PID bindings on the IS.
|
||||
const REACHABILITY_TIMEOUT = 10000; // ms
|
||||
|
@ -50,7 +53,7 @@ async function checkIdentityServerUrl(u) {
|
|||
if (response.ok) {
|
||||
return null;
|
||||
} else if (response.status < 200 || response.status >= 300) {
|
||||
return _t("Not a valid Identity Server (status code %(code)s)", {code: response.status});
|
||||
return _t("Not a valid Identity Server (status code %(code)s)", { code: response.status });
|
||||
} else {
|
||||
return _t("Could not connect to Identity Server");
|
||||
}
|
||||
|
@ -121,12 +124,11 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
private onIdentityServerChanged = (ev) => {
|
||||
const u = ev.target.value;
|
||||
|
||||
this.setState({idServer: u});
|
||||
this.setState({ idServer: u });
|
||||
};
|
||||
|
||||
private getTooltip = () => {
|
||||
if (this.state.checking) {
|
||||
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
|
||||
return <div>
|
||||
<InlineSpinner />
|
||||
{ _t("Checking server") }
|
||||
|
@ -159,14 +161,14 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
e.preventDefault();
|
||||
const { idServer, currentClientIdServer } = this.state;
|
||||
|
||||
this.setState({busy: true, checking: true, error: null});
|
||||
this.setState({ busy: true, checking: true, error: null });
|
||||
|
||||
const fullUrl = unabbreviateUrl(idServer);
|
||||
|
||||
let errStr = await checkIdentityServerUrl(fullUrl);
|
||||
if (!errStr) {
|
||||
try {
|
||||
this.setState({checking: false}); // clear tooltip
|
||||
this.setState({ checking: false }); // clear tooltip
|
||||
|
||||
// Test the identity server by trying to register with it. This
|
||||
// may result in a terms of service prompt.
|
||||
|
@ -217,7 +219,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
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"),
|
||||
description: (
|
||||
|
@ -236,13 +237,13 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private onDisconnectClicked = async () => {
|
||||
this.setState({disconnectBusy: true});
|
||||
this.setState({ disconnectBusy: true });
|
||||
try {
|
||||
const [confirmed] = await this.showServerChangeWarning({
|
||||
title: _t("Disconnect identity server"),
|
||||
unboundMessage: _t(
|
||||
"Disconnect from the identity server <idserver />?", {},
|
||||
{idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b>},
|
||||
{ idserver: sub => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b> },
|
||||
),
|
||||
button: _t("Disconnect"),
|
||||
});
|
||||
|
@ -250,7 +251,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
this.disconnectIdServer();
|
||||
}
|
||||
} finally {
|
||||
this.setState({disconnectBusy: false});
|
||||
this.setState({ disconnectBusy: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -319,7 +320,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
message = unboundMessage;
|
||||
}
|
||||
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
const { finished } = Modal.createTrackedDialog('Identity Server Bound Warning', '', QuestionDialog, {
|
||||
title,
|
||||
description: message,
|
||||
|
@ -352,8 +352,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const idServerUrl = this.state.currentClientIdServer;
|
||||
let sectionTitle;
|
||||
let bodyText;
|
||||
|
@ -369,7 +367,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
bodyText = _t(
|
||||
"If you don't want to use <server /> to discover and be discoverable by existing " +
|
||||
"contacts you know, enter another identity server below.",
|
||||
{}, {server: sub => <b>{abbreviateUrl(idServerUrl)}</b>},
|
||||
{}, { server: sub => <b>{abbreviateUrl(idServerUrl)}</b> },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -398,7 +396,6 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
|||
discoButtonContent = _t("Do not use an identity server");
|
||||
}
|
||||
if (this.state.disconnectBusy) {
|
||||
const InlineSpinner = sdk.getComponent('views.elements.InlineSpinner');
|
||||
discoButtonContent = <InlineSpinner />;
|
||||
}
|
||||
discoSection = <div>
|
||||
|
|
|
@ -15,17 +15,27 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
|
||||
import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance";
|
||||
import * as sdk from '../../../index';
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {SettingLevel} from "../../../settings/SettingLevel";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
|
||||
}
|
||||
|
||||
interface IState {
|
||||
currentManager: IntegrationManagerInstance;
|
||||
provisioningEnabled: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.settings.SetIntegrationManager")
|
||||
export default class SetIntegrationManager extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
export default class SetIntegrationManager extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const currentManager = IntegrationManagers.sharedInstance().getPrimaryManager();
|
||||
|
||||
|
@ -35,18 +45,18 @@ export default class SetIntegrationManager extends React.Component {
|
|||
};
|
||||
}
|
||||
|
||||
onProvisioningToggled = () => {
|
||||
private onProvisioningToggled = (): void => {
|
||||
const current = this.state.provisioningEnabled;
|
||||
SettingsStore.setValue("integrationProvisioning", null, SettingLevel.ACCOUNT, !current).catch(err => {
|
||||
console.error("Error changing integration manager provisioning");
|
||||
console.error(err);
|
||||
|
||||
this.setState({provisioningEnabled: current});
|
||||
this.setState({ provisioningEnabled: current });
|
||||
});
|
||||
this.setState({provisioningEnabled: !current});
|
||||
this.setState({ provisioningEnabled: !current });
|
||||
};
|
||||
|
||||
render() {
|
||||
public render(): React.ReactNode {
|
||||
const ToggleSwitch = sdk.getComponent("views.elements.ToggleSwitch");
|
||||
|
||||
const currentManager = this.state.currentManager;
|
||||
|
@ -57,7 +67,7 @@ export default class SetIntegrationManager extends React.Component {
|
|||
bodyText = _t(
|
||||
"Use an Integration Manager <b>(%(serverName)s)</b> to manage bots, widgets, " +
|
||||
"and sticker packs.",
|
||||
{serverName: currentManager.name},
|
||||
{ serverName: currentManager.name },
|
||||
{ b: sub => <b>{sub}</b> },
|
||||
);
|
||||
} else {
|
|
@ -17,21 +17,21 @@ limitations under the License.
|
|||
import React from 'react';
|
||||
import SpellCheckLanguagesDropdown from "../../../components/views/elements/SpellCheckLanguagesDropdown";
|
||||
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||
|
||||
interface ExistingSpellCheckLanguageIProps {
|
||||
language: string,
|
||||
onRemoved(language: string),
|
||||
language: string;
|
||||
onRemoved(language: string);
|
||||
}
|
||||
|
||||
interface SpellCheckLanguagesIProps {
|
||||
languages: Array<string>,
|
||||
onLanguagesChange(languages: Array<string>),
|
||||
languages: Array<string>;
|
||||
onLanguagesChange(languages: Array<string>);
|
||||
}
|
||||
|
||||
interface SpellCheckLanguagesIState {
|
||||
newLanguage: string,
|
||||
newLanguage: string;
|
||||
}
|
||||
|
||||
export class ExistingSpellCheckLanguage extends React.Component<ExistingSpellCheckLanguageIProps> {
|
||||
|
@ -60,7 +60,7 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
|||
super(props);
|
||||
this.state = {
|
||||
newLanguage: "",
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_onRemoved = (language) => {
|
||||
|
@ -77,13 +77,13 @@ export default class SpellCheckLanguages extends React.Component<SpellCheckLangu
|
|||
if (!language) return;
|
||||
if (this.props.languages.includes(language)) return;
|
||||
|
||||
this.props.languages.push(language)
|
||||
this.props.languages.push(language);
|
||||
this.props.onLanguagesChange(this.props.languages);
|
||||
};
|
||||
|
||||
_onNewLanguageChange = (language: string) => {
|
||||
if (this.state.newLanguage === language) return;
|
||||
this.setState({newLanguage: language});
|
||||
this.setState({ newLanguage: language });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
|
|
@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {useState} from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import {UpdateCheckStatus} from "../../../BasePlatform";
|
||||
import { UpdateCheckStatus } from "../../../BasePlatform";
|
||||
import PlatformPeg from "../../../PlatformPeg";
|
||||
import {useDispatcher} from "../../../hooks/useDispatcher";
|
||||
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||
import dis from "../../../dispatcher/dispatcher";
|
||||
import {Action} from "../../../dispatcher/actions";
|
||||
import {_t} from "../../../languageHandler";
|
||||
import { Action } from "../../../dispatcher/actions";
|
||||
import { _t } from "../../../languageHandler";
|
||||
import InlineSpinner from "../../../components/views/elements/InlineSpinner";
|
||||
import AccessibleButton from "../../../components/views/elements/AccessibleButton";
|
||||
import {CheckUpdatesPayload} from "../../../dispatcher/payloads/CheckUpdatesPayload";
|
||||
import { CheckUpdatesPayload } from "../../../dispatcher/payloads/CheckUpdatesPayload";
|
||||
|
||||
function installUpdate() {
|
||||
PlatformPeg.get().installUpdate();
|
||||
|
@ -61,7 +61,7 @@ const UpdateCheckButton = () => {
|
|||
PlatformPeg.get().startUpdateCheck();
|
||||
};
|
||||
|
||||
useDispatcher(dis, ({action, ...params}) => {
|
||||
useDispatcher(dis, ({ action, ...params }) => {
|
||||
if (action === Action.CheckUpdates) {
|
||||
setState(params as CheckUpdatesPayload);
|
||||
}
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import Field from "../../elements/Field";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import * as Email from "../../../../email";
|
||||
import AddThreepid from "../../../../AddThreepid";
|
||||
import * as sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -57,14 +57,14 @@ export class ExistingEmailAddress extends React.Component {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: true});
|
||||
this.setState({ verifyRemove: true });
|
||||
};
|
||||
|
||||
_onDontRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: false});
|
||||
this.setState({ verifyRemove: false });
|
||||
};
|
||||
|
||||
_onActuallyRemove = (e) => {
|
||||
|
@ -88,14 +88,20 @@ export class ExistingEmailAddress extends React.Component {
|
|||
return (
|
||||
<div className="mx_ExistingEmailAddress">
|
||||
<span className="mx_ExistingEmailAddress_promptText">
|
||||
{_t("Remove %(email)s?", {email: this.props.email.address} )}
|
||||
{_t("Remove %(email)s?", { email: this.props.email.address } )}
|
||||
</span>
|
||||
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn">
|
||||
<AccessibleButton
|
||||
onClick={this._onActuallyRemove}
|
||||
kind="danger_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn"
|
||||
>
|
||||
{_t("Remove")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this._onDontRemove} kind="link_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn">
|
||||
<AccessibleButton
|
||||
onClick={this._onDontRemove}
|
||||
kind="link_sm"
|
||||
className="mx_ExistingEmailAddress_confirmBtn"
|
||||
>
|
||||
{_t("Cancel")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
@ -161,13 +167,13 @@ export default class EmailAddresses extends React.Component {
|
|||
}
|
||||
|
||||
const task = new AddThreepid();
|
||||
this.setState({verifying: true, continueDisabled: true, addTask: task});
|
||||
this.setState({ verifying: true, continueDisabled: true, addTask: task });
|
||||
|
||||
task.addEmailAddress(email).then(() => {
|
||||
this.setState({continueDisabled: false});
|
||||
this.setState({ continueDisabled: false });
|
||||
}).catch((err) => {
|
||||
console.error("Unable to add email address " + email + " " + err);
|
||||
this.setState({verifying: false, continueDisabled: false, addTask: null});
|
||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||
Modal.createTrackedDialog('Unable to add email address', '', ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
|
@ -179,7 +185,7 @@ export default class EmailAddresses extends React.Component {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({continueDisabled: true});
|
||||
this.setState({ continueDisabled: true });
|
||||
this.state.addTask.checkEmailLinkClicked().then(([finished]) => {
|
||||
let newEmailAddress = this.state.newEmailAddress;
|
||||
if (finished) {
|
||||
|
@ -198,7 +204,7 @@ export default class EmailAddresses extends React.Component {
|
|||
newEmailAddress,
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.setState({continueDisabled: false});
|
||||
this.setState({ continueDisabled: false });
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
if (err.errcode === 'M_THREEPID_AUTH_FAILED') {
|
||||
Modal.createTrackedDialog("Email hasn't been verified yet", "", ErrorDialog, {
|
||||
|
@ -228,21 +234,28 @@ export default class EmailAddresses extends React.Component {
|
|||
);
|
||||
if (this.state.verifying) {
|
||||
addButton = (
|
||||
<div>
|
||||
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
|
||||
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
||||
disabled={this.state.continueDisabled}>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div>
|
||||
<div>{_t("We've sent you an email to verify your address. Please follow the instructions there and then click the button below.")}</div>
|
||||
<AccessibleButton
|
||||
onClick={this._onContinueClick}
|
||||
kind="primary"
|
||||
disabled={this.state.continueDisabled}
|
||||
>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_EmailAddresses">
|
||||
{existingEmailElements}
|
||||
<form onSubmit={this._onAddClick} autoComplete="off"
|
||||
noValidate={true} className="mx_EmailAddresses_new">
|
||||
<form
|
||||
onSubmit={this._onAddClick}
|
||||
autoComplete="off"
|
||||
noValidate={true}
|
||||
className="mx_EmailAddresses_new"
|
||||
>
|
||||
<Field
|
||||
type="text"
|
||||
label={_t("Email Address")}
|
||||
|
|
|
@ -17,15 +17,15 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import Field from "../../elements/Field";
|
||||
import AccessibleButton from "../../elements/AccessibleButton";
|
||||
import AddThreepid from "../../../../AddThreepid";
|
||||
import CountryDropdown from "../../auth/CountryDropdown";
|
||||
import * as sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -52,14 +52,14 @@ export class ExistingPhoneNumber extends React.Component {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: true});
|
||||
this.setState({ verifyRemove: true });
|
||||
};
|
||||
|
||||
_onDontRemove = (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({verifyRemove: false});
|
||||
this.setState({ verifyRemove: false });
|
||||
};
|
||||
|
||||
_onActuallyRemove = (e) => {
|
||||
|
@ -83,14 +83,20 @@ export class ExistingPhoneNumber extends React.Component {
|
|||
return (
|
||||
<div className="mx_ExistingPhoneNumber">
|
||||
<span className="mx_ExistingPhoneNumber_promptText">
|
||||
{_t("Remove %(phone)s?", {phone: this.props.msisdn.address})}
|
||||
{_t("Remove %(phone)s?", { phone: this.props.msisdn.address })}
|
||||
</span>
|
||||
<AccessibleButton onClick={this._onActuallyRemove} kind="danger_sm"
|
||||
className="mx_ExistingPhoneNumber_confirmBtn">
|
||||
<AccessibleButton
|
||||
onClick={this._onActuallyRemove}
|
||||
kind="danger_sm"
|
||||
className="mx_ExistingPhoneNumber_confirmBtn"
|
||||
>
|
||||
{_t("Remove")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={this._onDontRemove} kind="link_sm"
|
||||
className="mx_ExistingPhoneNumber_confirmBtn">
|
||||
<AccessibleButton
|
||||
onClick={this._onDontRemove}
|
||||
kind="link_sm"
|
||||
className="mx_ExistingPhoneNumber_confirmBtn"
|
||||
>
|
||||
{_t("Cancel")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
@ -158,13 +164,13 @@ export default class PhoneNumbers extends React.Component {
|
|||
const phoneCountry = this.state.phoneCountry;
|
||||
|
||||
const task = new AddThreepid();
|
||||
this.setState({verifying: true, continueDisabled: true, addTask: task});
|
||||
this.setState({ verifying: true, continueDisabled: true, addTask: task });
|
||||
|
||||
task.addMsisdn(phoneCountry, phoneNumber).then((response) => {
|
||||
this.setState({continueDisabled: false, verifyMsisdn: response.msisdn});
|
||||
this.setState({ continueDisabled: false, verifyMsisdn: response.msisdn });
|
||||
}).catch((err) => {
|
||||
console.error("Unable to add phone number " + phoneNumber + " " + err);
|
||||
this.setState({verifying: false, continueDisabled: false, addTask: null});
|
||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||
Modal.createTrackedDialog('Add Phone Number Error', '', ErrorDialog, {
|
||||
title: _t("Error"),
|
||||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
|
@ -176,7 +182,7 @@ export default class PhoneNumbers extends React.Component {
|
|||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({continueDisabled: true});
|
||||
this.setState({ continueDisabled: true });
|
||||
const token = this.state.newPhoneNumberCode;
|
||||
const address = this.state.verifyMsisdn;
|
||||
this.state.addTask.haveMsisdnToken(token).then(([finished]) => {
|
||||
|
@ -199,7 +205,7 @@ export default class PhoneNumbers extends React.Component {
|
|||
newPhoneNumberCode: "",
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.setState({continueDisabled: false});
|
||||
this.setState({ continueDisabled: false });
|
||||
if (err.errcode !== 'M_THREEPID_AUTH_FAILED') {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
console.error("Unable to verify phone number: " + err);
|
||||
|
@ -208,13 +214,13 @@ export default class PhoneNumbers extends React.Component {
|
|||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
} else {
|
||||
this.setState({verifyError: _t("Incorrect verification code")});
|
||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
_onCountryChanged = (e) => {
|
||||
this.setState({phoneCountry: e.iso2});
|
||||
this.setState({ phoneCountry: e.iso2 });
|
||||
};
|
||||
|
||||
render() {
|
||||
|
@ -246,8 +252,11 @@ export default class PhoneNumbers extends React.Component {
|
|||
value={this.state.newPhoneNumberCode}
|
||||
onChange={this._onChangeNewPhoneNumberCode}
|
||||
/>
|
||||
<AccessibleButton onClick={this._onContinueClick} kind="primary"
|
||||
disabled={this.state.continueDisabled}>
|
||||
<AccessibleButton
|
||||
onClick={this._onContinueClick}
|
||||
kind="primary"
|
||||
disabled={this.state.continueDisabled}
|
||||
>
|
||||
{_t("Continue")}
|
||||
</AccessibleButton>
|
||||
</form>
|
||||
|
|
|
@ -19,11 +19,11 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import * as sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import AddThreepid from '../../../../AddThreepid';
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
|
|
@ -19,11 +19,11 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { _t } from "../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../../MatrixClientPeg";
|
||||
import * as sdk from '../../../../index';
|
||||
import Modal from '../../../../Modal';
|
||||
import AddThreepid from '../../../../AddThreepid';
|
||||
import {replaceableComponent} from "../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../utils/replaceableComponent";
|
||||
|
||||
/*
|
||||
TODO: Improve the UX for everything in here.
|
||||
|
@ -190,7 +190,7 @@ export class PhoneNumber extends React.Component {
|
|||
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||
});
|
||||
} else {
|
||||
this.setState({verifyError: _t("Incorrect verification code")});
|
||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,68 +15,76 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../..";
|
||||
import { EventType } from 'matrix-js-sdk/src/@types/event';
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import RoomUpgradeDialog from "../../../dialogs/RoomUpgradeDialog";
|
||||
import DevtoolsDialog from "../../../dialogs/DevtoolsDialog";
|
||||
import Modal from "../../../../../Modal";
|
||||
import dis from "../../../../../dispatcher/dispatcher";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
interface IProps {
|
||||
roomId: string;
|
||||
closeSettingsFn(): void;
|
||||
}
|
||||
|
||||
interface IRecommendedVersion {
|
||||
version: string;
|
||||
needsUpgrade: boolean;
|
||||
urgent: boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
upgradeRecommendation?: IRecommendedVersion;
|
||||
oldRoomId?: string;
|
||||
oldEventId?: string;
|
||||
upgraded?: boolean;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.settings.tabs.room.AdvancedRoomSettingsTab")
|
||||
export default class AdvancedRoomSettingsTab extends React.Component {
|
||||
static propTypes = {
|
||||
roomId: PropTypes.string.isRequired,
|
||||
closeSettingsFn: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
export default class AdvancedRoomSettingsTab extends React.Component<IProps, IState> {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
// This is eventually set to the value of room.getRecommendedVersion()
|
||||
upgradeRecommendation: null,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: [REACT-WARNING] Move this to constructor
|
||||
UNSAFE_componentWillMount() { // eslint-disable-line camelcase
|
||||
// we handle lack of this object gracefully later, so don't worry about it failing here.
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
room.getRecommendedVersion().then((v) => {
|
||||
const tombstone = room.currentState.getStateEvents("m.room.tombstone", "");
|
||||
const tombstone = room.currentState.getStateEvents(EventType.RoomTombstone, "");
|
||||
|
||||
const additionalStateChanges = {};
|
||||
const createEvent = room.currentState.getStateEvents("m.room.create", "");
|
||||
const additionalStateChanges: Partial<IState> = {};
|
||||
const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, "");
|
||||
const predecessor = createEvent ? createEvent.getContent().predecessor : null;
|
||||
if (predecessor && predecessor.room_id) {
|
||||
additionalStateChanges['oldRoomId'] = predecessor.room_id;
|
||||
additionalStateChanges['oldEventId'] = predecessor.event_id;
|
||||
additionalStateChanges['hasPreviousRoom'] = true;
|
||||
additionalStateChanges.oldRoomId = predecessor.room_id;
|
||||
additionalStateChanges.oldEventId = predecessor.event_id;
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
upgraded: tombstone && tombstone.getContent().replacement_room,
|
||||
upgraded: !!tombstone?.getContent().replacement_room,
|
||||
upgradeRecommendation: v,
|
||||
...additionalStateChanges,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_upgradeRoom = (e) => {
|
||||
const RoomUpgradeDialog = sdk.getComponent('dialogs.RoomUpgradeDialog');
|
||||
private upgradeRoom = (e) => {
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, {room: room});
|
||||
Modal.createTrackedDialog('Upgrade Room Version', '', RoomUpgradeDialog, { room });
|
||||
};
|
||||
|
||||
_openDevtools = (e) => {
|
||||
const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
|
||||
Modal.createDialog(DevtoolsDialog, {roomId: this.props.roomId});
|
||||
private openDevtools = (e) => {
|
||||
Modal.createDialog(DevtoolsDialog, { roomId: this.props.roomId });
|
||||
};
|
||||
|
||||
_onOldRoomClicked = (e) => {
|
||||
private onOldRoomClicked = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -93,9 +101,9 @@ export default class AdvancedRoomSettingsTab extends React.Component {
|
|||
const room = client.getRoom(this.props.roomId);
|
||||
|
||||
let unfederatableSection;
|
||||
const createEvent = room.currentState.getStateEvents('m.room.create', '');
|
||||
const createEvent = room.currentState.getStateEvents(EventType.RoomCreate, '');
|
||||
if (createEvent && createEvent.getContent()['m.federate'] === false) {
|
||||
unfederatableSection = <div>{_t('This room is not accessible by remote Matrix servers')}</div>;
|
||||
unfederatableSection = <div>{ _t('This room is not accessible by remote Matrix servers') }</div>;
|
||||
}
|
||||
|
||||
let roomUpgradeButton;
|
||||
|
@ -103,7 +111,7 @@ export default class AdvancedRoomSettingsTab extends React.Component {
|
|||
roomUpgradeButton = (
|
||||
<div>
|
||||
<p className='mx_SettingsTab_warningText'>
|
||||
{_t(
|
||||
{ _t(
|
||||
"<b>Warning</b>: Upgrading a room will <i>not automatically migrate room members " +
|
||||
"to the new version of the room.</i> We'll post a link to the new room in the old " +
|
||||
"version of the room - room members will have to click this link to join the new room.",
|
||||
|
@ -111,51 +119,53 @@ export default class AdvancedRoomSettingsTab extends React.Component {
|
|||
"b": (sub) => <b>{sub}</b>,
|
||||
"i": (sub) => <i>{sub}</i>,
|
||||
},
|
||||
)}
|
||||
) }
|
||||
</p>
|
||||
<AccessibleButton onClick={this._upgradeRoom} kind='primary'>
|
||||
{_t("Upgrade this room to the recommended room version")}
|
||||
<AccessibleButton onClick={this.upgradeRoom} kind='primary'>
|
||||
{ _t("Upgrade this room to the recommended room version") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let oldRoomLink;
|
||||
if (this.state.hasPreviousRoom) {
|
||||
if (this.state.oldRoomId) {
|
||||
let name = _t("this room");
|
||||
const room = MatrixClientPeg.get().getRoom(this.props.roomId);
|
||||
if (room && room.name) name = room.name;
|
||||
oldRoomLink = (
|
||||
<AccessibleButton element='a' onClick={this._onOldRoomClicked}>
|
||||
{_t("View older messages in %(roomName)s.", {roomName: name})}
|
||||
<AccessibleButton element='a' onClick={this.onOldRoomClicked}>
|
||||
{ _t("View older messages in %(roomName)s.", { roomName: name }) }
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Advanced")}</div>
|
||||
<div className="mx_SettingsTab_heading">{ _t("Advanced") }</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Room information")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>
|
||||
{ room?.isSpaceRoom() ? _t("Space information") : _t("Room information") }
|
||||
</span>
|
||||
<div>
|
||||
<span>{_t("Internal room ID:")}</span>
|
||||
{this.props.roomId}
|
||||
<span>{ _t("Internal room ID:") }</span>
|
||||
{ this.props.roomId }
|
||||
</div>
|
||||
{unfederatableSection}
|
||||
{ unfederatableSection }
|
||||
</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Room version")}</span>
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Room version") }</span>
|
||||
<div>
|
||||
<span>{_t("Room version:")}</span>
|
||||
{room.getVersion()}
|
||||
<span>{ _t("Room version:") }</span>
|
||||
{ room.getVersion() }
|
||||
</div>
|
||||
{oldRoomLink}
|
||||
{roomUpgradeButton}
|
||||
{ oldRoomLink }
|
||||
{ roomUpgradeButton }
|
||||
</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Developer options")}</span>
|
||||
<AccessibleButton onClick={this._openDevtools} kind='primary'>
|
||||
{_t("Open Devtools")}
|
||||
<span className='mx_SettingsTab_subheading'>{ _t("Developer options") }</span>
|
||||
<AccessibleButton onClick={this.openDevtools} kind='primary'>
|
||||
{ _t("Open Devtools") }
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</div>
|
|
@ -15,13 +15,13 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import {Room} from "matrix-js-sdk/src/models/room";
|
||||
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
|
||||
import { Room } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import BridgeTile from "../../BridgeTile";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
const BRIDGE_EVENT_TYPES = [
|
||||
"uk.half-shot.bridge",
|
||||
|
@ -44,14 +44,11 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
|||
return <BridgeTile key={event.getId()} room={room} ev={event} />;
|
||||
}
|
||||
|
||||
static getBridgeStateEvents(roomId: string) {
|
||||
static getBridgeStateEvents(roomId: string): MatrixEvent[] {
|
||||
const client = MatrixClientPeg.get();
|
||||
const roomState = client.getRoom(roomId).currentState;
|
||||
|
||||
return BRIDGE_EVENT_TYPES.map(typeName => {
|
||||
const events = roomState.events.get(typeName);
|
||||
return events ? Array.from(events.values()) : [];
|
||||
}).flat(1);
|
||||
return BRIDGE_EVENT_TYPES.map(typeName => roomState.getStateEvents(typeName)).flat(1);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -16,15 +16,15 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
|
||||
import * as sdk from "../../../../..";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import dis from "../../../../../dispatcher/dispatcher";
|
||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.room.GeneralRoomSettingsTab")
|
||||
export default class GeneralRoomSettingsTab extends React.Component {
|
||||
|
@ -60,7 +60,6 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
const canSetAliases = true; // Previously, we arbitrarily only allowed admins to do this
|
||||
const canSetCanonical = room.currentState.mayClientSendStateEvent("m.room.canonical_alias", client);
|
||||
const canonicalAliasEv = room.currentState.getStateEvents("m.room.canonical_alias", '');
|
||||
const aliasEvents = room.currentState.getStateEvents("m.room.aliases");
|
||||
|
||||
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
|
||||
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
|
||||
|
@ -80,9 +79,11 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
flairSection = <>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Flair")}</span>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<RelatedGroupSettings roomId={room.roomId}
|
||||
canSetRelatedGroups={canChangeGroups}
|
||||
relatedGroupsEvent={groupsEvent} />
|
||||
<RelatedGroupSettings
|
||||
roomId={room.roomId}
|
||||
canSetRelatedGroups={canChangeGroups}
|
||||
relatedGroupsEvent={groupsEvent}
|
||||
/>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
@ -97,8 +98,8 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
|||
<div className="mx_SettingsTab_heading">{_t("Room Addresses")}</div>
|
||||
<div className='mx_SettingsTab_section mx_SettingsTab_subsectionText'>
|
||||
<AliasSettings roomId={this.props.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv} aliasEvents={aliasEvents} />
|
||||
canSetCanonicalAlias={canSetCanonical} canSetAliases={canSetAliases}
|
||||
canonicalAliasEvent={canonicalAliasEv} />
|
||||
</div>
|
||||
<div className="mx_SettingsTab_heading">{_t("Other")}</div>
|
||||
{ flairSection }
|
||||
|
|
|
@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {createRef} from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Notifier from "../../../../../Notifier";
|
||||
import SettingsStore from '../../../../../settings/SettingsStore';
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.room.NotificationsSettingsTab")
|
||||
export default class NotificationsSettingsTab extends React.Component {
|
||||
|
@ -47,7 +47,7 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
if (!soundData) {
|
||||
return;
|
||||
}
|
||||
this.setState({currentSound: soundData.name || soundData.url});
|
||||
this.setState({ currentSound: soundData.name || soundData.url });
|
||||
}
|
||||
|
||||
async _triggerUploader(e) {
|
||||
|
@ -155,7 +155,7 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
<div>
|
||||
<span>{_t("Notification sound")}: <code>{this.state.currentSound}</code></span><br />
|
||||
<AccessibleButton className="mx_NotificationSound_resetSound" disabled={this.state.currentSound == "default"} onClick={this._clearSound.bind(this)} kind="primary">
|
||||
{_t("Reset")}
|
||||
{_t("Reset")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -167,11 +167,11 @@ export default class NotificationsSettingsTab extends React.Component {
|
|||
{currentUploadedFile}
|
||||
|
||||
<AccessibleButton className="mx_NotificationSound_browse" onClick={this._triggerUploader.bind(this)} kind="primary">
|
||||
{_t("Browse")}
|
||||
{_t("Browse")}
|
||||
</AccessibleButton>
|
||||
|
||||
<AccessibleButton className="mx_NotificationSound_save" disabled={this.state.uploadedFile == null} onClick={this._onClickSaveSound.bind(this)} kind="primary">
|
||||
{_t("Save")}
|
||||
{_t("Save")}
|
||||
</AccessibleButton>
|
||||
<br />
|
||||
</div>
|
||||
|
|
|
@ -15,16 +15,18 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t, _td} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../..";
|
||||
import { _t, _td } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Modal from "../../../../../Modal";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import {EventType} from "matrix-js-sdk/src/@types/event";
|
||||
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";
|
||||
import { compare } from "../../../../../utils/strings";
|
||||
import ErrorDialog from '../../../dialogs/ErrorDialog';
|
||||
import PowerSelector from "../../../elements/PowerSelector";
|
||||
|
||||
const plEventsToLabels = {
|
||||
// These will be translated for us later.
|
||||
|
@ -44,18 +46,18 @@ const plEventsToLabels = {
|
|||
|
||||
const plEventsToShow = {
|
||||
// If an event is listed here, it will be shown in the PL settings. Defaults will be calculated.
|
||||
[EventType.RoomAvatar]: {isState: true},
|
||||
[EventType.RoomName]: {isState: true},
|
||||
[EventType.RoomCanonicalAlias]: {isState: true},
|
||||
[EventType.RoomHistoryVisibility]: {isState: true},
|
||||
[EventType.RoomPowerLevels]: {isState: true},
|
||||
[EventType.RoomTopic]: {isState: true},
|
||||
[EventType.RoomTombstone]: {isState: true},
|
||||
[EventType.RoomEncryption]: {isState: true},
|
||||
[EventType.RoomServerAcl]: {isState: true},
|
||||
[EventType.RoomAvatar]: { isState: true },
|
||||
[EventType.RoomName]: { isState: true },
|
||||
[EventType.RoomCanonicalAlias]: { isState: true },
|
||||
[EventType.RoomHistoryVisibility]: { isState: true },
|
||||
[EventType.RoomPowerLevels]: { isState: true },
|
||||
[EventType.RoomTopic]: { isState: true },
|
||||
[EventType.RoomTombstone]: { isState: true },
|
||||
[EventType.RoomEncryption]: { isState: true },
|
||||
[EventType.RoomServerAcl]: { isState: true },
|
||||
|
||||
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
|
||||
"im.vector.modular.widgets": {isState: true},
|
||||
"im.vector.modular.widgets": { isState: true },
|
||||
};
|
||||
|
||||
// parse a string as an integer; if the input is undefined, or cannot be parsed
|
||||
|
@ -75,7 +77,6 @@ interface IBannedUserProps {
|
|||
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);
|
||||
Modal.createTrackedDialog('Failed to unban', '', ErrorDialog, {
|
||||
title: _t('Error'),
|
||||
|
@ -102,7 +103,7 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
|||
return (
|
||||
<li>
|
||||
{unbanButton}
|
||||
<span title={_t("Banned by %(displayName)s", {displayName: this.props.by})}>
|
||||
<span title={_t("Banned by %(displayName)s", { displayName: this.props.by })}>
|
||||
<strong>{ this.props.member.name }</strong> {userId}
|
||||
{this.props.reason ? " " + _t('Reason') + ": " + this.props.reason : ""}
|
||||
</span>
|
||||
|
@ -175,7 +176,6 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
client.sendStateEvent(this.props.roomId, "m.room.power_levels", plContent).catch(e => {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Power level requirement change failed', '', ErrorDialog, {
|
||||
title: _t('Error changing power level requirement'),
|
||||
description: _t(
|
||||
|
@ -202,7 +202,6 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
client.sendStateEvent(this.props.roomId, "m.room.power_levels", plContent).catch(e => {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Power level change failed', '', ErrorDialog, {
|
||||
title: _t('Error changing power level'),
|
||||
description: _t(
|
||||
|
@ -214,8 +213,6 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
const plEvent = room.currentState.getStateEvents('m.room.power_levels', '');
|
||||
|
@ -283,6 +280,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
const mutedUsers = [];
|
||||
|
||||
Object.keys(userLevels).forEach((user) => {
|
||||
if (!Number.isInteger(userLevels[user])) { return; }
|
||||
const canChange = userLevels[user] < currentUserLevel && canChangeLevels;
|
||||
if (userLevels[user] > defaultUserLevel) { // privileged
|
||||
privilegedUsers.push(
|
||||
|
@ -312,7 +310,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
// comparator for sorting PL users lexicographically on PL descending, MXID ascending. (case-insensitive)
|
||||
const comparator = (a, b) => {
|
||||
const plDiff = userLevels[b.key] - userLevels[a.key];
|
||||
return plDiff !== 0 ? plDiff : a.key.toLocaleLowerCase().localeCompare(b.key.toLocaleLowerCase());
|
||||
return plDiff !== 0 ? plDiff : compare(a.key.toLocaleLowerCase(), b.key.toLocaleLowerCase());
|
||||
};
|
||||
|
||||
privilegedUsers.sort(comparator);
|
||||
|
@ -393,7 +391,7 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
|||
if (label) {
|
||||
label = _t(label);
|
||||
} else {
|
||||
label = _t("Send %(eventType)s events", {eventType});
|
||||
label = _t("Send %(eventType)s events", { eventType });
|
||||
}
|
||||
return (
|
||||
<div className="" key={eventType}>
|
||||
|
|
|
@ -16,32 +16,35 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../..";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import Modal from "../../../../../Modal";
|
||||
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||
import StyledRadioGroup from '../../../elements/StyledRadioGroup';
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import SettingsFlag from '../../../elements/SettingsFlag';
|
||||
|
||||
// Knock and private are reserved keywords which are not yet implemented.
|
||||
enum JoinRule {
|
||||
export enum JoinRule {
|
||||
Public = "public",
|
||||
Knock = "knock",
|
||||
Invite = "invite",
|
||||
/**
|
||||
* @deprecated Reserved. Should not be used.
|
||||
*/
|
||||
Private = "private",
|
||||
}
|
||||
|
||||
enum GuestAccess {
|
||||
export enum GuestAccess {
|
||||
CanJoin = "can_join",
|
||||
Forbidden = "forbidden",
|
||||
}
|
||||
|
||||
enum HistoryVisibility {
|
||||
export enum HistoryVisibility {
|
||||
Invited = "invited",
|
||||
Joined = "joined",
|
||||
Shared = "shared",
|
||||
|
@ -97,9 +100,9 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
HistoryVisibility.Shared,
|
||||
);
|
||||
const encrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.roomId);
|
||||
this.setState({joinRule, guestAccess, history, encrypted});
|
||||
this.setState({ joinRule, guestAccess, history, encrypted });
|
||||
const hasAliases = await this.hasAliases();
|
||||
this.setState({hasAliases});
|
||||
this.setState({ hasAliases });
|
||||
}
|
||||
|
||||
private pullContentPropertyFromEvent<T>(event: MatrixEvent, key: string, defaultValue: T): T {
|
||||
|
@ -121,7 +124,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
if (refreshWhenTypes.includes(e.getType())) this.forceUpdate();
|
||||
};
|
||||
|
||||
private onEncryptionChange = (e: React.ChangeEvent) => {
|
||||
private onEncryptionChange = () => {
|
||||
Modal.createTrackedDialog('Enable encryption', '', QuestionDialog, {
|
||||
title: _t('Enable encryption?'),
|
||||
description: _t(
|
||||
|
@ -137,18 +140,18 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
),
|
||||
onFinished: (confirm) => {
|
||||
if (!confirm) {
|
||||
this.setState({encrypted: false});
|
||||
this.setState({ encrypted: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const beforeEncrypted = this.state.encrypted;
|
||||
this.setState({encrypted: true});
|
||||
this.setState({ encrypted: true });
|
||||
MatrixClientPeg.get().sendStateEvent(
|
||||
this.props.roomId, "m.room.encryption",
|
||||
{ algorithm: "m.megolm.v1.aes-sha2" },
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({encrypted: beforeEncrypted});
|
||||
this.setState({ encrypted: beforeEncrypted });
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -163,16 +166,26 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
const beforeJoinRule = this.state.joinRule;
|
||||
const beforeGuestAccess = this.state.guestAccess;
|
||||
this.setState({joinRule, guestAccess});
|
||||
this.setState({ joinRule, guestAccess });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => {
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.join_rules",
|
||||
{ join_rule: joinRule },
|
||||
"",
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({joinRule: beforeJoinRule});
|
||||
this.setState({ joinRule: beforeJoinRule });
|
||||
});
|
||||
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => {
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.guest_access",
|
||||
{ guest_access: guestAccess },
|
||||
"",
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({guestAccess: beforeGuestAccess});
|
||||
this.setState({ guestAccess: beforeGuestAccess });
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -209,27 +222,37 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
|
||||
const beforeJoinRule = this.state.joinRule;
|
||||
const beforeGuestAccess = this.state.guestAccess;
|
||||
this.setState({joinRule, guestAccess});
|
||||
this.setState({ joinRule, guestAccess });
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
client.sendStateEvent(this.props.roomId, "m.room.join_rules", {join_rule: joinRule}, "").catch((e) => {
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.join_rules",
|
||||
{ join_rule: joinRule },
|
||||
"",
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({joinRule: beforeJoinRule});
|
||||
this.setState({ joinRule: beforeJoinRule });
|
||||
});
|
||||
client.sendStateEvent(this.props.roomId, "m.room.guest_access", {guest_access: guestAccess}, "").catch((e) => {
|
||||
client.sendStateEvent(
|
||||
this.props.roomId,
|
||||
"m.room.guest_access",
|
||||
{ guest_access: guestAccess },
|
||||
"",
|
||||
).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({guestAccess: beforeGuestAccess});
|
||||
this.setState({ guestAccess: beforeGuestAccess });
|
||||
});
|
||||
};
|
||||
|
||||
private onHistoryRadioToggle = (history: HistoryVisibility) => {
|
||||
const beforeHistory = this.state.history;
|
||||
this.setState({history: history});
|
||||
this.setState({ history: history });
|
||||
MatrixClientPeg.get().sendStateEvent(this.props.roomId, "m.room.history_visibility", {
|
||||
history_visibility: history,
|
||||
}, "").catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({history: beforeHistory});
|
||||
this.setState({ history: beforeHistory });
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -368,8 +391,6 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
|||
}
|
||||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
|
||||
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client.getRoom(this.props.roomId);
|
||||
const isEncrypted = this.state.encrypted;
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import { MatrixClientPeg } from '../../../../../MatrixClientPeg';
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
|
@ -35,9 +35,10 @@ import Field from '../../../elements/Field';
|
|||
import EventTilePreview from '../../../elements/EventTilePreview';
|
||||
import StyledRadioGroup from "../../../elements/StyledRadioGroup";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||
import {Layout} from "../../../../../settings/Layout";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { Layout } from "../../../../../settings/Layout";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import { compare } from "../../../../../utils/strings";
|
||||
|
||||
interface IProps {
|
||||
}
|
||||
|
@ -83,7 +84,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
fontSize: (SettingsStore.getValue("baseFontSize", null) + FontWatcher.SIZE_DIFF).toString(),
|
||||
...this.calculateThemeState(),
|
||||
customThemeUrl: "",
|
||||
customThemeMessage: {isError: false, text: ""},
|
||||
customThemeMessage: { isError: false, text: "" },
|
||||
useCustomFontSize: SettingsStore.getValue("useCustomFontSize"),
|
||||
useSystemFont: SettingsStore.getValue("useSystemFont"),
|
||||
systemFont: SettingsStore.getValue("systemFont"),
|
||||
|
@ -148,43 +149,43 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
// so remember what the value was before we tried to set it so we can revert
|
||||
const oldTheme: string = SettingsStore.getValue('theme');
|
||||
SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme).catch(() => {
|
||||
dis.dispatch<RecheckThemePayload>({action: Action.RecheckTheme});
|
||||
this.setState({theme: oldTheme});
|
||||
dis.dispatch<RecheckThemePayload>({ action: Action.RecheckTheme });
|
||||
this.setState({ theme: oldTheme });
|
||||
});
|
||||
this.setState({theme: newTheme});
|
||||
this.setState({ theme: newTheme });
|
||||
// The settings watcher doesn't fire until the echo comes back from the
|
||||
// server, so to make the theme change immediately we need to manually
|
||||
// do the dispatch now
|
||||
// XXX: The local echoed value appears to be unreliable, in particular
|
||||
// when settings custom themes(!) so adding forceTheme to override
|
||||
// the value from settings.
|
||||
dis.dispatch<RecheckThemePayload>({action: Action.RecheckTheme, forceTheme: newTheme});
|
||||
dis.dispatch<RecheckThemePayload>({ action: Action.RecheckTheme, forceTheme: newTheme });
|
||||
};
|
||||
|
||||
private onUseSystemThemeChanged = (checked: boolean): void => {
|
||||
this.setState({useSystemTheme: checked});
|
||||
this.setState({ useSystemTheme: checked });
|
||||
SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked);
|
||||
dis.dispatch<RecheckThemePayload>({action: Action.RecheckTheme});
|
||||
dis.dispatch<RecheckThemePayload>({ action: Action.RecheckTheme });
|
||||
};
|
||||
|
||||
private onFontSizeChanged = (size: number): void => {
|
||||
this.setState({fontSize: size.toString()});
|
||||
this.setState({ fontSize: size.toString() });
|
||||
SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF);
|
||||
};
|
||||
|
||||
private onValidateFontSize = async ({value}: Pick<IFieldState, "value">): Promise<IValidationResult> => {
|
||||
private onValidateFontSize = async ({ value }: Pick<IFieldState, "value">): Promise<IValidationResult> => {
|
||||
const parsedSize = parseFloat(value);
|
||||
const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF;
|
||||
const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF;
|
||||
|
||||
if (isNaN(parsedSize)) {
|
||||
return {valid: false, feedback: _t("Size must be a number")};
|
||||
return { valid: false, feedback: _t("Size must be a number") };
|
||||
}
|
||||
|
||||
if (!(min <= parsedSize && parsedSize <= max)) {
|
||||
return {
|
||||
valid: false,
|
||||
feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', {min, max}),
|
||||
feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', { min, max }),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -195,7 +196,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
parseInt(value, 10) - FontWatcher.SIZE_DIFF,
|
||||
);
|
||||
|
||||
return {valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', {min, max})};
|
||||
return { valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', { min, max }) };
|
||||
};
|
||||
|
||||
private onAddCustomTheme = async (): Promise<void> => {
|
||||
|
@ -212,37 +213,37 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
// XXX: need some schema for this
|
||||
const themeInfo = await r.json();
|
||||
if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') {
|
||||
this.setState({customThemeMessage: {text: _t("Invalid theme schema."), isError: true}});
|
||||
this.setState({ customThemeMessage: { text: _t("Invalid theme schema."), isError: true } });
|
||||
return;
|
||||
}
|
||||
currentThemes.push(themeInfo);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.setState({customThemeMessage: {text: _t("Error downloading theme information."), isError: true}});
|
||||
this.setState({ customThemeMessage: { text: _t("Error downloading theme information."), isError: true } });
|
||||
return; // Don't continue on error
|
||||
}
|
||||
|
||||
await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
|
||||
this.setState({customThemeUrl: "", customThemeMessage: {text: _t("Theme added!"), isError: false}});
|
||||
this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } });
|
||||
|
||||
this.themeTimer = setTimeout(() => {
|
||||
this.setState({customThemeMessage: {text: "", isError: false}});
|
||||
this.setState({ customThemeMessage: { text: "", isError: false } });
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
private onCustomThemeChange = (e: React.ChangeEvent<HTMLSelectElement | HTMLInputElement>): void => {
|
||||
this.setState({customThemeUrl: e.target.value});
|
||||
this.setState({ customThemeUrl: e.target.value });
|
||||
};
|
||||
|
||||
private onIRCLayoutChange = (enabled: boolean) => {
|
||||
if (enabled) {
|
||||
this.setState({layout: Layout.IRC});
|
||||
this.setState({ layout: Layout.IRC });
|
||||
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
||||
} else {
|
||||
this.setState({layout: Layout.Group});
|
||||
this.setState({ layout: Layout.Group });
|
||||
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private renderThemeSection() {
|
||||
const themeWatcher = new ThemeWatcher();
|
||||
|
@ -292,10 +293,10 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
|
||||
// XXX: replace any type here
|
||||
const themes = Object.entries<any>(enumerateThemes())
|
||||
.map(p => ({id: p[0], name: p[1]})); // convert pairs to objects for code readability
|
||||
.map(p => ({ id: p[0], name: p[1] })); // convert pairs to objects for code readability
|
||||
const builtInThemes = themes.filter(p => !p.id.startsWith("custom-"));
|
||||
const customThemes = themes.filter(p => !builtInThemes.includes(p))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
.sort((a, b) => compare(a.name, b.name));
|
||||
const orderedThemes = [...builtInThemes, ...customThemes];
|
||||
return (
|
||||
<div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_themeSection">
|
||||
|
@ -347,7 +348,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
<SettingsFlag
|
||||
name="useCustomFontSize"
|
||||
level={SettingLevel.ACCOUNT}
|
||||
onChange={(checked) => this.setState({useCustomFontSize: checked})}
|
||||
onChange={(checked) => this.setState({ useCustomFontSize: checked })}
|
||||
useCheckbox={true}
|
||||
/>
|
||||
|
||||
|
@ -359,7 +360,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
value={this.state.fontSize.toString()}
|
||||
id="font_size_field"
|
||||
onValidate={this.onValidateFontSize}
|
||||
onChange={(value) => this.setState({fontSize: value.target.value})}
|
||||
onChange={(value) => this.setState({ fontSize: value.target.value })}
|
||||
disabled={!this.state.useCustomFontSize}
|
||||
className="mx_SettingsTab_customFontSizeField"
|
||||
/>
|
||||
|
@ -372,7 +373,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
const brand = SdkConfig.get().brand;
|
||||
const toggle = <div
|
||||
className="mx_AppearanceUserSettingsTab_AdvancedToggle"
|
||||
onClick={() => this.setState({showAdvanced: !this.state.showAdvanced})}
|
||||
onClick={() => this.setState({ showAdvanced: !this.state.showAdvanced })}
|
||||
>
|
||||
{this.state.showAdvanced ? _t("Hide advanced") : _t("Show advanced")}
|
||||
</div>;
|
||||
|
@ -402,7 +403,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
|||
name="useSystemFont"
|
||||
level={SettingLevel.DEVICE}
|
||||
useCheckbox={true}
|
||||
onChange={(checked) => this.setState({useSystemFont: checked})}
|
||||
onChange={(checked) => this.setState({ useSystemFont: checked })}
|
||||
/>
|
||||
<Field
|
||||
className="mx_AppearanceUserSettingsTab_systemFont"
|
||||
|
|
|
@ -15,9 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import GroupUserSettings from "../../../groups/GroupUserSettings";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.user.FlairUserSettingsTab")
|
||||
export default class FlairUserSettingsTab extends React.Component {
|
||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import ProfileSettings from "../../ProfileSettings";
|
||||
import * as languageHandler from "../../../../../languageHandler";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
|
@ -27,19 +27,19 @@ import AccessibleButton from "../../../elements/AccessibleButton";
|
|||
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
|
||||
import PropTypes from "prop-types";
|
||||
import PlatformPeg from "../../../../../PlatformPeg";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../..";
|
||||
import Modal from "../../../../../Modal";
|
||||
import dis from "../../../../../dispatcher/dispatcher";
|
||||
import {Service, startTermsFlow} from "../../../../../Terms";
|
||||
import {SERVICE_TYPES} from "matrix-js-sdk/src/service-types";
|
||||
import { Service, startTermsFlow } from "../../../../../Terms";
|
||||
import { SERVICE_TYPES } from "matrix-js-sdk/src/service-types";
|
||||
import IdentityAuthClient from "../../../../../IdentityAuthClient";
|
||||
import {abbreviateUrl} from "../../../../../utils/UrlUtils";
|
||||
import { abbreviateUrl } from "../../../../../utils/UrlUtils";
|
||||
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
|
||||
import Spinner from "../../../elements/Spinner";
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.user.GeneralUserSettingsTab")
|
||||
export default class GeneralUserSettingsTab extends React.Component {
|
||||
|
@ -84,7 +84,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
// the enabled flag value.
|
||||
const canChangePassword = !changePasswordCap || changePasswordCap['enabled'] !== false;
|
||||
|
||||
this.setState({serverSupportsSeparateAddAndBind, canChangePassword});
|
||||
this.setState({ serverSupportsSeparateAddAndBind, canChangePassword });
|
||||
|
||||
this._getThreepidState();
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
_onAction = (payload) => {
|
||||
if (payload.action === 'id_server_changed') {
|
||||
this.setState({haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl())});
|
||||
this.setState({ haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()) });
|
||||
this._getThreepidState();
|
||||
}
|
||||
};
|
||||
|
@ -145,7 +145,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
|
||||
async _checkTerms() {
|
||||
if (!this.state.haveIdServer) {
|
||||
this.setState({idServerHasUnsignedTerms: false});
|
||||
this.setState({ idServerHasUnsignedTerms: false });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
if (this.state.language === newLanguage) return;
|
||||
|
||||
SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage);
|
||||
this.setState({language: newLanguage});
|
||||
this.setState({ language: newLanguage });
|
||||
const platform = PlatformPeg.get();
|
||||
if (platform) {
|
||||
platform.setLanguage(newLanguage);
|
||||
|
@ -200,7 +200,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
};
|
||||
|
||||
_onSpellCheckLanguagesChange = (languages) => {
|
||||
this.setState({spellCheckLanguages: languages});
|
||||
this.setState({ spellCheckLanguages: languages });
|
||||
|
||||
const plaf = PlatformPeg.get();
|
||||
if (plaf) {
|
||||
|
@ -323,8 +323,11 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Language and region")}</span>
|
||||
<LanguageDropdown className="mx_GeneralUserSettingsTab_languageInput"
|
||||
onOptionChange={this._onLanguageChange} value={this.state.language} />
|
||||
<LanguageDropdown
|
||||
className="mx_GeneralUserSettingsTab_languageInput"
|
||||
onOptionChange={this._onLanguageChange}
|
||||
value={this.state.language}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -333,8 +336,10 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
return (
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Spell check dictionaries")}</span>
|
||||
<SpellCheckSettings languages={this.state.spellCheckLanguages}
|
||||
onLanguagesChange={this._onSpellCheckLanguagesChange} />
|
||||
<SpellCheckSettings
|
||||
languages={this.state.spellCheckLanguages}
|
||||
onLanguagesChange={this._onSpellCheckLanguagesChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -348,7 +353,7 @@ export default class GeneralUserSettingsTab extends React.Component {
|
|||
{_t(
|
||||
"Agree to the identity server (%(serverName)s) Terms of Service to " +
|
||||
"allow yourself to be discoverable by email address or phone number.",
|
||||
{serverName: this.state.idServerName},
|
||||
{ serverName: this.state.idServerName },
|
||||
)}
|
||||
</span>;
|
||||
return (
|
||||
|
|
|
@ -15,20 +15,25 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t, getCurrentLanguage} from "../../../../../languageHandler";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import { _t, getCurrentLanguage } from "../../../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import AccessibleTooltipButton from '../../../elements/AccessibleTooltipButton';
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import createRoom from "../../../../../createRoom";
|
||||
import Modal from "../../../../../Modal";
|
||||
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 * as ContextMenu from "../../../../structures/ContextMenu";
|
||||
import { toRightOf } from "../../../../structures/ContextMenu";
|
||||
import BugReportDialog from '../../../dialogs/BugReportDialog';
|
||||
import GenericTextContextMenu from "../../../context_menus/GenericTextContextMenu";
|
||||
|
||||
interface IProps {
|
||||
closeSettingsFn: () => {};
|
||||
closeSettingsFn: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
|
@ -38,6 +43,8 @@ interface IState {
|
|||
|
||||
@replaceableComponent("views.settings.tabs.user.HelpUserSettingsTab")
|
||||
export default class HelpUserSettingsTab extends React.Component<IProps, IState> {
|
||||
protected closeCopiedTooltip: () => void;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
|
@ -48,14 +55,20 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
PlatformPeg.get().getAppVersion().then((ver) => this.setState({appVersion: ver})).catch((e) => {
|
||||
PlatformPeg.get().getAppVersion().then((ver) => this.setState({ appVersion: ver })).catch((e) => {
|
||||
console.error("Error getting vector version: ", e);
|
||||
});
|
||||
PlatformPeg.get().canSelfUpdate().then((v) => this.setState({canUpdate: v})).catch((e) => {
|
||||
PlatformPeg.get().canSelfUpdate().then((v) => this.setState({ canUpdate: v })).catch((e) => {
|
||||
console.error("Error getting self updatability: ", e);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// if the Copied tooltip is open then get rid of it, there are ways to close the modal which wouldn't close
|
||||
// the tooltip otherwise, such as pressing Escape
|
||||
if (this.closeCopiedTooltip) this.closeCopiedTooltip();
|
||||
}
|
||||
|
||||
private onClearCacheAndReload = (e) => {
|
||||
if (!PlatformPeg.get()) return;
|
||||
|
||||
|
@ -69,10 +82,6 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
};
|
||||
|
||||
private onBugReport = (e) => {
|
||||
const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog");
|
||||
if (!BugReportDialog) {
|
||||
return;
|
||||
}
|
||||
Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, {});
|
||||
};
|
||||
|
||||
|
@ -153,6 +162,19 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
);
|
||||
}
|
||||
|
||||
onAccessTokenCopyClick = async (e) => {
|
||||
e.preventDefault();
|
||||
const target = e.target; // copy target before we go async and React throws it away
|
||||
|
||||
const successful = await copyPlaintext(MatrixClientPeg.get().getAccessToken());
|
||||
const buttonRect = target.getBoundingClientRect();
|
||||
const { close } = ContextMenu.createMenu(GenericTextContextMenu, {
|
||||
...toRightOf(buttonRect, 2),
|
||||
message: successful ? _t('Copied!') : _t('Failed to copy'),
|
||||
});
|
||||
this.closeCopiedTooltip = target.onmouseleave = close;
|
||||
};
|
||||
|
||||
render() {
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
|
@ -269,12 +291,20 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
|||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_t("Homeserver is")} <code>{MatrixClientPeg.get().getHomeserverUrl()}</code><br />
|
||||
{_t("Identity Server is")} <code>{MatrixClientPeg.get().getIdentityServerUrl()}</code><br />
|
||||
{_t("Access Token:") + ' '}
|
||||
<AccessibleButton element="span" onClick={this.showSpoiler}
|
||||
data-spoiler={MatrixClientPeg.get().getAccessToken()}
|
||||
>
|
||||
<{ _t("click to reveal") }>
|
||||
</AccessibleButton>
|
||||
<br />
|
||||
<details>
|
||||
<summary>{_t("Access Token")}</summary><br />
|
||||
<b>{_t("Your access token gives full access to your account."
|
||||
+ " Do not share it with anyone." )}</b>
|
||||
<div className="mx_HelpUserSettingsTab_accessToken">
|
||||
<code>{MatrixClientPeg.get().getAccessToken()}</code>
|
||||
<AccessibleTooltipButton
|
||||
title={_t("Copy")}
|
||||
onClick={this.onAccessTokenCopyClick}
|
||||
className="mx_HelpUserSettingsTab_accessToken_copy"
|
||||
/>
|
||||
</div>
|
||||
</details><br />
|
||||
<div className='mx_HelpUserSettingsTab_debugButton'>
|
||||
<AccessibleButton onClick={this.onClearCacheAndReload} kind='danger'>
|
||||
{_t("Clear cache and reload")}
|
||||
|
|
|
@ -15,13 +15,15 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import PropTypes from "prop-types";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import * as sdk from "../../../../../index";
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import BetaCard from "../../../beta/BetaCard";
|
||||
|
||||
export class LabsSettingToggle extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -48,14 +50,40 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
const flags = SettingsStore.getFeatureSettingNames().map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
const features = SettingsStore.getFeatureSettingNames();
|
||||
const [labs, betas] = features.reduce((arr, f) => {
|
||||
arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f);
|
||||
return arr;
|
||||
}, [[], []]);
|
||||
|
||||
let betaSection;
|
||||
if (betas.length) {
|
||||
betaSection = <div className="mx_SettingsTab_section">
|
||||
{ betas.map(f => <BetaCard key={f} featureId={f} /> ) }
|
||||
</div>;
|
||||
}
|
||||
|
||||
let labsSection;
|
||||
if (SdkConfig.get()['showLabsSettings']) {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
const flags = labs.map(f => <LabsSettingToggle featureId={f} key={f} />);
|
||||
|
||||
labsSection = <div className="mx_SettingsTab_section">
|
||||
{flags}
|
||||
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="lowBandwidth" level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name="advancedRoomListLogging" level={SettingLevel.DEVICE} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab">
|
||||
<div className="mx_SettingsTab mx_LabsUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Labs")}</div>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{
|
||||
_t('Customise your experience with experimental labs features. ' +
|
||||
_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"
|
||||
|
@ -64,13 +92,8 @@ export default class LabsUserSettingsTab extends React.Component {
|
|||
})
|
||||
}
|
||||
</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{flags}
|
||||
<SettingsFlag name={"enableWidgetScreenshots"} level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag name={"showHiddenEventsInTimeline"} level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name={"lowBandwidth"} level={SettingLevel.DEVICE} />
|
||||
<SettingsFlag name={"advancedRoomListLogging"} level={SettingLevel.DEVICE} />
|
||||
</div>
|
||||
{ betaSection }
|
||||
{ labsSection }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -15,15 +15,18 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import {Mjolnir} from "../../../../../mjolnir/Mjolnir";
|
||||
import {ListRule} from "../../../../../mjolnir/ListRule";
|
||||
import {BanList, RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
|
||||
import { Mjolnir } from "../../../../../mjolnir/Mjolnir";
|
||||
import { ListRule } from "../../../../../mjolnir/ListRule";
|
||||
import { BanList, RULE_SERVER, RULE_USER } from "../../../../../mjolnir/BanList";
|
||||
import Modal from "../../../../../Modal";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../../index";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import ErrorDialog from "../../../dialogs/ErrorDialog";
|
||||
import QuestionDialog from "../../../dialogs/QuestionDialog";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Field from "../../../elements/Field";
|
||||
|
||||
interface IState {
|
||||
busy: boolean;
|
||||
|
@ -44,11 +47,11 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
}
|
||||
|
||||
private onPersonalRuleChanged = (e) => {
|
||||
this.setState({newPersonalRule: e.target.value});
|
||||
this.setState({ newPersonalRule: e.target.value });
|
||||
};
|
||||
|
||||
private onNewListChanged = (e) => {
|
||||
this.setState({newList: e.target.value});
|
||||
this.setState({ newList: e.target.value });
|
||||
};
|
||||
|
||||
private onAddPersonalRule = async (e) => {
|
||||
|
@ -60,21 +63,20 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
kind = RULE_USER;
|
||||
}
|
||||
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const list = await Mjolnir.sharedInstance().getOrCreatePersonalList();
|
||||
await list.banEntity(kind, this.state.newPersonalRule, _t("Ignored/Blocked"));
|
||||
this.setState({newPersonalRule: ""}); // this will also cause the new rule to be rendered
|
||||
this.setState({ newPersonalRule: "" }); // this will also cause the new rule to be rendered
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to add Mjolnir rule', '', ErrorDialog, {
|
||||
title: _t('Error adding ignored user/server'),
|
||||
description: _t('Something went wrong. Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -82,63 +84,58 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const room = await MatrixClientPeg.get().joinRoom(this.state.newList);
|
||||
await Mjolnir.sharedInstance().subscribeToList(room.roomId);
|
||||
this.setState({newList: ""}); // this will also cause the new rule to be rendered
|
||||
this.setState({ newList: "" }); // this will also cause the new rule to be rendered
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to subscribe to Mjolnir list', '', ErrorDialog, {
|
||||
title: _t('Error subscribing to list'),
|
||||
description: _t('Please verify the room ID or address and try again.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
};
|
||||
|
||||
private async removePersonalRule(rule: ListRule) {
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
const list = Mjolnir.sharedInstance().getPersonalList();
|
||||
await list.unbanEntity(rule.kind, rule.entity);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, {
|
||||
title: _t('Error removing ignored user/server'),
|
||||
description: _t('Something went wrong. Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
private async unsubscribeFromList(list: BanList) {
|
||||
this.setState({busy: true});
|
||||
this.setState({ busy: true });
|
||||
try {
|
||||
await Mjolnir.sharedInstance().unsubscribeFromList(list.roomId);
|
||||
await MatrixClientPeg.get().leave(list.roomId);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Failed to unsubscribe from Mjolnir list', '', ErrorDialog, {
|
||||
title: _t('Error unsubscribing from list'),
|
||||
description: _t('Please try again or view your console for hints.'),
|
||||
});
|
||||
} finally {
|
||||
this.setState({busy: false});
|
||||
this.setState({ busy: false });
|
||||
}
|
||||
}
|
||||
|
||||
private viewListRules(list: BanList) {
|
||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||
|
||||
const room = MatrixClientPeg.get().getRoom(list.roomId);
|
||||
const name = room ? room.name : list.roomId;
|
||||
|
||||
|
@ -153,7 +150,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
};
|
||||
|
||||
Modal.createTrackedDialog('View Mjolnir list rules', '', QuestionDialog, {
|
||||
title: _t("Ban list rules - %(roomName)s", {roomName: name}),
|
||||
title: _t("Ban list rules - %(roomName)s", { roomName: name }),
|
||||
description: (
|
||||
<div>
|
||||
<h3>{_t("Server rules")}</h3>
|
||||
|
@ -168,8 +165,6 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
}
|
||||
|
||||
private renderPersonalBanListRules() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const list = Mjolnir.sharedInstance().getPersonalList();
|
||||
const rules = list ? [...list.userRules, ...list.serverRules] : [];
|
||||
if (!list || rules.length <= 0) return <i>{_t("You have not ignored anyone.")}</i>;
|
||||
|
@ -199,8 +194,6 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
}
|
||||
|
||||
private renderSubscribedBanLists() {
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
const personalList = Mjolnir.sharedInstance().getPersonalList();
|
||||
const lists = Mjolnir.sharedInstance().lists.filter(b => {
|
||||
return personalList? personalList.roomId !== b.roomId : true;
|
||||
|
@ -241,8 +234,6 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
}
|
||||
|
||||
render() {
|
||||
const Field = sdk.getComponent('elements.Field');
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
const brand = SdkConfig.get().brand;
|
||||
|
||||
return (
|
||||
|
@ -256,7 +247,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
|||
"Add users and servers you want to ignore here. Use asterisks " +
|
||||
"to have %(brand)s match any characters. For example, <code>@bot:*</code> " +
|
||||
"would ignore all users that have the name 'bot' on any server.",
|
||||
{ brand }, {code: (s) => <code>{s}</code>},
|
||||
{ brand }, { code: (s) => <code>{s}</code> },
|
||||
)}<br />
|
||||
<br />
|
||||
{_t(
|
||||
|
|
|
@ -15,9 +15,9 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import * as sdk from "../../../../../index";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.user.NotificationUserSettingsTab")
|
||||
export default class NotificationUserSettingsTab extends React.Component {
|
||||
|
|
|
@ -16,14 +16,16 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import Field from "../../../elements/Field";
|
||||
import * as sdk from "../../../../..";
|
||||
import PlatformPeg from "../../../../../PlatformPeg";
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import SettingsFlag from '../../../elements/SettingsFlag';
|
||||
import * as KeyboardShortcuts from "../../../../../accessibility/KeyboardShortcuts";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
|
||||
interface IState {
|
||||
autoLaunch: boolean;
|
||||
|
@ -45,6 +47,10 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
|||
'breadcrumbs',
|
||||
];
|
||||
|
||||
static KEYBINDINGS_SETTINGS = [
|
||||
'ctrlFForSearch',
|
||||
];
|
||||
|
||||
static COMPOSER_SETTINGS = [
|
||||
'MessageComposerInput.autoReplaceEmoji',
|
||||
'MessageComposerInput.suggestEmoji',
|
||||
|
@ -53,28 +59,32 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
|||
'MessageComposerInput.showStickersButton',
|
||||
];
|
||||
|
||||
static TIMELINE_SETTINGS = [
|
||||
'showTypingNotifications',
|
||||
'autoplayGifsAndVideos',
|
||||
'urlPreviewsEnabled',
|
||||
'TextualBody.enableBigEmoji',
|
||||
'showReadReceipts',
|
||||
static TIME_SETTINGS = [
|
||||
'showTwelveHourTimestamps',
|
||||
'alwaysShowTimestamps',
|
||||
'showRedactions',
|
||||
];
|
||||
static CODE_BLOCKS_SETTINGS = [
|
||||
'enableSyntaxHighlightLanguageDetection',
|
||||
'expandCodeByDefault',
|
||||
'scrollToBottomOnMessageSent',
|
||||
'showCodeLineNumbers',
|
||||
'showJoinLeaves',
|
||||
'showAvatarChanges',
|
||||
'showDisplaynameChanges',
|
||||
'showImages',
|
||||
'showChatEffects',
|
||||
'Pill.shouldShowPillAvatar',
|
||||
'ctrlFForSearch',
|
||||
];
|
||||
|
||||
static IMAGES_AND_VIDEOS_SETTINGS = [
|
||||
'urlPreviewsEnabled',
|
||||
'autoplayGifsAndVideos',
|
||||
'showImages',
|
||||
];
|
||||
static TIMELINE_SETTINGS = [
|
||||
'showTypingNotifications',
|
||||
'showRedactions',
|
||||
'showReadReceipts',
|
||||
'showJoinLeaves',
|
||||
'showDisplaynameChanges',
|
||||
'showChatEffects',
|
||||
'showAvatarChanges',
|
||||
'Pill.shouldShowPillAvatar',
|
||||
'TextualBody.enableBigEmoji',
|
||||
'scrollToBottomOnMessageSent',
|
||||
];
|
||||
static GENERAL_SETTINGS = [
|
||||
'TagPanel.enableTagPanel',
|
||||
'promptBeforeInviteUnknownUsers',
|
||||
|
@ -143,38 +153,37 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
|||
}
|
||||
|
||||
private onAutoLaunchChange = (checked: boolean) => {
|
||||
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({autoLaunch: checked}));
|
||||
PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({ autoLaunch: checked }));
|
||||
};
|
||||
|
||||
private onWarnBeforeExitChange = (checked: boolean) => {
|
||||
PlatformPeg.get().setWarnBeforeExit(checked).then(() => this.setState({warnBeforeExit: checked}));
|
||||
}
|
||||
PlatformPeg.get().setWarnBeforeExit(checked).then(() => this.setState({ warnBeforeExit: checked }));
|
||||
};
|
||||
|
||||
private onAlwaysShowMenuBarChange = (checked: boolean) => {
|
||||
PlatformPeg.get().setAutoHideMenuBarEnabled(!checked).then(() => this.setState({alwaysShowMenuBar: checked}));
|
||||
PlatformPeg.get().setAutoHideMenuBarEnabled(!checked).then(() => this.setState({ alwaysShowMenuBar: checked }));
|
||||
};
|
||||
|
||||
private onMinimizeToTrayChange = (checked: boolean) => {
|
||||
PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({minimizeToTray: checked}));
|
||||
PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({ minimizeToTray: checked }));
|
||||
};
|
||||
|
||||
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({autocompleteDelay: e.target.value});
|
||||
this.setState({ autocompleteDelay: e.target.value });
|
||||
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({readMarkerInViewThresholdMs: e.target.value});
|
||||
this.setState({ readMarkerInViewThresholdMs: e.target.value });
|
||||
SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({readMarkerOutOfViewThresholdMs: e.target.value});
|
||||
this.setState({ readMarkerOutOfViewThresholdMs: e.target.value });
|
||||
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
|
||||
};
|
||||
|
||||
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} />;
|
||||
});
|
||||
|
@ -222,11 +231,34 @@ export default class PreferencesUserSettingsTab extends React.Component<{}, ISta
|
|||
{this.renderGroup(PreferencesUserSettingsTab.ROOM_LIST_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Keyboard shortcuts")}</span>
|
||||
<AccessibleButton className="mx_SettingsFlag" onClick={KeyboardShortcuts.toggleDialog}>
|
||||
{ _t("To view all keyboard shortcuts, click here.") }
|
||||
</AccessibleButton>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.KEYBINDINGS_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Displaying time")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Composer")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Code blocks")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.CODE_BLOCKS_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Images, GIFs and videos")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.IMAGES_AND_VIDEOS_SETTINGS)}
|
||||
</div>
|
||||
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Timeline")}</span>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)}
|
||||
|
|
|
@ -17,24 +17,25 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import Analytics from "../../../../../Analytics";
|
||||
import Modal from "../../../../../Modal";
|
||||
import * as sdk from "../../../../..";
|
||||
import {sleep} from "../../../../../utils/promise";
|
||||
import dis from "../../../../../dispatcher/dispatcher";
|
||||
import {privateShouldBeEncrypted} from "../../../../../createRoom";
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import { privateShouldBeEncrypted } from "../../../../../createRoom";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import SecureBackupPanel from "../../SecureBackupPanel";
|
||||
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||
import {isE2eAdvancedPanelPossible} from "../../E2eAdvancedPanel";
|
||||
import { UIFeature } from "../../../../../settings/UIFeature";
|
||||
import { isE2eAdvancedPanelPossible } from "../../E2eAdvancedPanel";
|
||||
import CountlyAnalytics from "../../../../../CountlyAnalytics";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
|
||||
export class IgnoredUser extends React.Component {
|
||||
static propTypes = {
|
||||
|
@ -82,12 +83,11 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
this._onAction = this._onAction.bind(this);
|
||||
}
|
||||
|
||||
|
||||
_onAction({action}) {
|
||||
_onAction({ action }) {
|
||||
if (action === "ignore_state_changed") {
|
||||
const ignoredUserIds = MatrixClientPeg.get().getIgnoredUsers();
|
||||
const newWaitingUnignored = this.state.waitingUnignored.filter(e=> ignoredUserIds.includes(e));
|
||||
this.setState({ignoredUserIds, waitingUnignored: newWaitingUnignored});
|
||||
this.setState({ ignoredUserIds, waitingUnignored: newWaitingUnignored });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,14 +111,14 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
_onExportE2eKeysClicked = () => {
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||
import('../../../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||
{matrixClient: MatrixClientPeg.get()},
|
||||
{ matrixClient: MatrixClientPeg.get() },
|
||||
);
|
||||
};
|
||||
|
||||
_onImportE2eKeysClicked = () => {
|
||||
Modal.createTrackedDialogAsync('Import E2E Keys', '',
|
||||
import('../../../../../async-components/views/dialogs/security/ImportE2eKeysDialog'),
|
||||
{matrixClient: MatrixClientPeg.get()},
|
||||
{ matrixClient: MatrixClientPeg.get() },
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -131,13 +131,13 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
}
|
||||
|
||||
_onUserUnignored = async (userId) => {
|
||||
const {ignoredUserIds, waitingUnignored} = this.state;
|
||||
const { ignoredUserIds, waitingUnignored } = this.state;
|
||||
const currentlyIgnoredUserIds = ignoredUserIds.filter(e => !waitingUnignored.includes(e));
|
||||
|
||||
const index = currentlyIgnoredUserIds.indexOf(userId);
|
||||
if (index !== -1) {
|
||||
currentlyIgnoredUserIds.splice(index, 1);
|
||||
this.setState(({waitingUnignored}) => ({waitingUnignored: [...waitingUnignored, userId]}));
|
||||
this.setState(({ waitingUnignored }) => ({ waitingUnignored: [...waitingUnignored, userId] }));
|
||||
MatrixClientPeg.get().setIgnoredUsers(currentlyIgnoredUserIds);
|
||||
}
|
||||
};
|
||||
|
@ -168,7 +168,7 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
// Accept/reject invite
|
||||
await action(roomId).then(() => {
|
||||
// No error, update invited rooms button
|
||||
this.setState({invitedRoomAmt: self.state.invitedRoomAmt - 1});
|
||||
this.setState({ invitedRoomAmt: self.state.invitedRoomAmt - 1 });
|
||||
}, async (e) => {
|
||||
// Action failure
|
||||
if (e.errcode === "M_LIMIT_EXCEEDED") {
|
||||
|
@ -253,16 +253,20 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
}
|
||||
|
||||
_renderIgnoredUsers() {
|
||||
const {waitingUnignored, ignoredUserIds} = this.state;
|
||||
const { waitingUnignored, ignoredUserIds } = this.state;
|
||||
|
||||
const userIds = !ignoredUserIds?.length
|
||||
? _t('You have no ignored users.')
|
||||
: ignoredUserIds.map((u) => <IgnoredUser
|
||||
userId={u}
|
||||
onUnignored={this._onUserUnignored}
|
||||
key={u}
|
||||
inProgress={waitingUnignored.includes(u)}
|
||||
/>);
|
||||
: ignoredUserIds.map((u) => {
|
||||
return (
|
||||
<IgnoredUser
|
||||
userId={u}
|
||||
onUnignored={this._onUserUnignored}
|
||||
key={u}
|
||||
inProgress={waitingUnignored.includes(u)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
|
@ -287,10 +291,10 @@ export default class SecurityUserSettingsTab extends React.Component {
|
|||
<div className='mx_SettingsTab_section mx_SecurityUserSettingsTab_bulkOptions'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bulk options')}</span>
|
||||
<AccessibleButton onClick={onClickAccept} kind='primary' disabled={this.state.managingInvites}>
|
||||
{_t("Accept all %(invitedRooms)s invites", {invitedRooms: this.state.invitedRoomAmt})}
|
||||
{_t("Accept all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt })}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={onClickReject} kind='danger' disabled={this.state.managingInvites}>
|
||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: this.state.invitedRoomAmt})}
|
||||
{_t("Reject all %(invitedRooms)s invites", { invitedRooms: this.state.invitedRoomAmt })}
|
||||
</AccessibleButton>
|
||||
{this.state.managingInvites ? <InlineSpinner /> : <div />}
|
||||
</div>
|
||||
|
|
|
@ -1,234 +0,0 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {_t} from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import CallMediaHandler from "../../../../../CallMediaHandler";
|
||||
import Field from "../../../elements/Field";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
|
||||
import * as sdk from "../../../../../index";
|
||||
import Modal from "../../../../../Modal";
|
||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||
import {replaceableComponent} from "../../../../../utils/replaceableComponent";
|
||||
|
||||
@replaceableComponent("views.settings.tabs.user.VoiceUserSettingsTab")
|
||||
export default class VoiceUserSettingsTab extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
mediaDevices: false,
|
||||
activeAudioOutput: null,
|
||||
activeAudioInput: null,
|
||||
activeVideoInput: null,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const canSeeDeviceLabels = await CallMediaHandler.hasAnyLabeledDevices();
|
||||
if (canSeeDeviceLabels) {
|
||||
this._refreshMediaDevices();
|
||||
}
|
||||
}
|
||||
|
||||
_refreshMediaDevices = async (stream) => {
|
||||
this.setState({
|
||||
mediaDevices: await CallMediaHandler.getDevices(),
|
||||
activeAudioOutput: CallMediaHandler.getAudioOutput(),
|
||||
activeAudioInput: CallMediaHandler.getAudioInput(),
|
||||
activeVideoInput: CallMediaHandler.getVideoInput(),
|
||||
});
|
||||
if (stream) {
|
||||
// kill stream (after we've enumerated the devices, otherwise we'd get empty labels again)
|
||||
// so that we don't leave it lingering around with webcam enabled etc
|
||||
// as here we called gUM to ask user for permission to their device names only
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
};
|
||||
|
||||
_requestMediaPermissions = async () => {
|
||||
let constraints;
|
||||
let stream;
|
||||
let error;
|
||||
try {
|
||||
constraints = {video: true, audio: true};
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
} catch (err) {
|
||||
// user likely doesn't have a webcam,
|
||||
// we should still allow to select a microphone
|
||||
if (err.name === "NotFoundError") {
|
||||
constraints = { audio: true };
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
} else {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
console.log("Failed to list userMedia devices", error);
|
||||
const brand = SdkConfig.get().brand;
|
||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||
Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {
|
||||
title: _t('No media permissions'),
|
||||
description: _t(
|
||||
'You may need to manually permit %(brand)s to access your microphone/webcam',
|
||||
{ brand },
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this._refreshMediaDevices(stream);
|
||||
}
|
||||
};
|
||||
|
||||
_setAudioOutput = (e) => {
|
||||
CallMediaHandler.setAudioOutput(e.target.value);
|
||||
this.setState({
|
||||
activeAudioOutput: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
_setAudioInput = (e) => {
|
||||
CallMediaHandler.setAudioInput(e.target.value);
|
||||
this.setState({
|
||||
activeAudioInput: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
_setVideoInput = (e) => {
|
||||
CallMediaHandler.setVideoInput(e.target.value);
|
||||
this.setState({
|
||||
activeVideoInput: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
_changeWebRtcMethod = (p2p) => {
|
||||
MatrixClientPeg.get().setForceTURN(!p2p);
|
||||
};
|
||||
|
||||
_changeFallbackICEServerAllowed = (allow) => {
|
||||
MatrixClientPeg.get().setFallbackICEServerAllowed(allow);
|
||||
};
|
||||
|
||||
_renderDeviceOptions(devices, category) {
|
||||
return devices.map((d) => {
|
||||
return (<option key={`${category}-${d.deviceId}`} value={d.deviceId}>{d.label}</option>);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||
|
||||
let requestButton = null;
|
||||
let speakerDropdown = null;
|
||||
let microphoneDropdown = null;
|
||||
let webcamDropdown = null;
|
||||
if (this.state.mediaDevices === false) {
|
||||
requestButton = (
|
||||
<div className='mx_VoiceUserSettingsTab_missingMediaPermissions'>
|
||||
<p>{_t("Missing media permissions, click the button below to request.")}</p>
|
||||
<AccessibleButton onClick={this._requestMediaPermissions} kind="primary">
|
||||
{_t("Request media permissions")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.mediaDevices) {
|
||||
speakerDropdown = <p>{ _t('No Audio Outputs detected') }</p>;
|
||||
microphoneDropdown = <p>{ _t('No Microphones detected') }</p>;
|
||||
webcamDropdown = <p>{ _t('No Webcams detected') }</p>;
|
||||
|
||||
const defaultOption = {
|
||||
deviceId: '',
|
||||
label: _t('Default Device'),
|
||||
};
|
||||
const getDefaultDevice = (devices) => {
|
||||
// Note we're looking for a device with deviceId 'default' but adding a device
|
||||
// with deviceId == the empty string: this is because Chrome gives us a device
|
||||
// with deviceId 'default', so we're looking for this, not the one we are adding.
|
||||
if (!devices.some((i) => i.deviceId === 'default')) {
|
||||
devices.unshift(defaultOption);
|
||||
return '';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
const audioOutputs = this.state.mediaDevices.audiooutput.slice(0);
|
||||
if (audioOutputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(audioOutputs);
|
||||
speakerDropdown = (
|
||||
<Field element="select" label={_t("Audio Output")}
|
||||
value={this.state.activeAudioOutput || defaultDevice}
|
||||
onChange={this._setAudioOutput}>
|
||||
{this._renderDeviceOptions(audioOutputs, 'audioOutput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
const audioInputs = this.state.mediaDevices.audioinput.slice(0);
|
||||
if (audioInputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(audioInputs);
|
||||
microphoneDropdown = (
|
||||
<Field element="select" label={_t("Microphone")}
|
||||
value={this.state.activeAudioInput || defaultDevice}
|
||||
onChange={this._setAudioInput}>
|
||||
{this._renderDeviceOptions(audioInputs, 'audioInput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
const videoInputs = this.state.mediaDevices.videoinput.slice(0);
|
||||
if (videoInputs.length > 0) {
|
||||
const defaultDevice = getDefaultDevice(videoInputs);
|
||||
webcamDropdown = (
|
||||
<Field element="select" label={_t("Camera")}
|
||||
value={this.state.activeVideoInput || defaultDevice}
|
||||
onChange={this._setVideoInput}>
|
||||
{this._renderDeviceOptions(videoInputs, 'videoInput')}
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_VoiceUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{requestButton}
|
||||
{speakerDropdown}
|
||||
{microphoneDropdown}
|
||||
{webcamDropdown}
|
||||
<SettingsFlag name='VideoView.flipVideoHorizontally' level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag
|
||||
name='webRtcAllowPeerToPeer'
|
||||
level={SettingLevel.DEVICE}
|
||||
onChange={this._changeWebRtcMethod}
|
||||
/>
|
||||
<SettingsFlag
|
||||
name='fallbackICEServerAllowed'
|
||||
level={SettingLevel.DEVICE}
|
||||
onChange={this._changeFallbackICEServerAllowed}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
206
src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx
Normal file
206
src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
Copyright 2020 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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { _t } from "../../../../../languageHandler";
|
||||
import SdkConfig from "../../../../../SdkConfig";
|
||||
import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler";
|
||||
import Field from "../../../elements/Field";
|
||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
|
||||
import Modal from "../../../../../Modal";
|
||||
import { SettingLevel } from "../../../../../settings/SettingLevel";
|
||||
import { replaceableComponent } from "../../../../../utils/replaceableComponent";
|
||||
import SettingsFlag from '../../../elements/SettingsFlag';
|
||||
import ErrorDialog from '../../../dialogs/ErrorDialog';
|
||||
|
||||
const getDefaultDevice = (devices: Array<Partial<MediaDeviceInfo>>) => {
|
||||
// Note we're looking for a device with deviceId 'default' but adding a device
|
||||
// with deviceId == the empty string: this is because Chrome gives us a device
|
||||
// with deviceId 'default', so we're looking for this, not the one we are adding.
|
||||
if (!devices.some((i) => i.deviceId === 'default')) {
|
||||
devices.unshift({ deviceId: '', label: _t('Default Device') });
|
||||
return '';
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
};
|
||||
|
||||
interface IState extends Record<MediaDeviceKindEnum, string> {
|
||||
mediaDevices: IMediaDevices;
|
||||
}
|
||||
|
||||
@replaceableComponent("views.settings.tabs.user.VoiceUserSettingsTab")
|
||||
export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
mediaDevices: null,
|
||||
[MediaDeviceKindEnum.AudioOutput]: null,
|
||||
[MediaDeviceKindEnum.AudioInput]: null,
|
||||
[MediaDeviceKindEnum.VideoInput]: null,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const canSeeDeviceLabels = await MediaDeviceHandler.hasAnyLabeledDevices();
|
||||
if (canSeeDeviceLabels) {
|
||||
this.refreshMediaDevices();
|
||||
}
|
||||
}
|
||||
|
||||
private refreshMediaDevices = async (stream?: MediaStream): Promise<void> => {
|
||||
this.setState({
|
||||
mediaDevices: await MediaDeviceHandler.getDevices(),
|
||||
[MediaDeviceKindEnum.AudioOutput]: MediaDeviceHandler.getAudioOutput(),
|
||||
[MediaDeviceKindEnum.AudioInput]: MediaDeviceHandler.getAudioInput(),
|
||||
[MediaDeviceKindEnum.VideoInput]: MediaDeviceHandler.getVideoInput(),
|
||||
});
|
||||
if (stream) {
|
||||
// kill stream (after we've enumerated the devices, otherwise we'd get empty labels again)
|
||||
// so that we don't leave it lingering around with webcam enabled etc
|
||||
// as here we called gUM to ask user for permission to their device names only
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
}
|
||||
};
|
||||
|
||||
private requestMediaPermissions = async (): Promise<void> => {
|
||||
let constraints;
|
||||
let stream;
|
||||
let error;
|
||||
try {
|
||||
constraints = { video: true, audio: true };
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
} catch (err) {
|
||||
// user likely doesn't have a webcam,
|
||||
// we should still allow to select a microphone
|
||||
if (err.name === "NotFoundError") {
|
||||
constraints = { audio: true };
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
} else {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
console.log("Failed to list userMedia devices", error);
|
||||
const brand = SdkConfig.get().brand;
|
||||
Modal.createTrackedDialog('No media permissions', '', ErrorDialog, {
|
||||
title: _t('No media permissions'),
|
||||
description: _t(
|
||||
'You may need to manually permit %(brand)s to access your microphone/webcam',
|
||||
{ brand },
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this.refreshMediaDevices(stream);
|
||||
}
|
||||
};
|
||||
|
||||
private setDevice = (deviceId: string, kind: MediaDeviceKindEnum): void => {
|
||||
MediaDeviceHandler.instance.setDevice(deviceId, kind);
|
||||
this.setState<null>({ [kind]: deviceId });
|
||||
};
|
||||
|
||||
private changeWebRtcMethod = (p2p: boolean): void => {
|
||||
MatrixClientPeg.get().setForceTURN(!p2p);
|
||||
};
|
||||
|
||||
private changeFallbackICEServerAllowed = (allow: boolean): void => {
|
||||
MatrixClientPeg.get().setFallbackICEServerAllowed(allow);
|
||||
};
|
||||
|
||||
private renderDeviceOptions(devices: Array<MediaDeviceInfo>, category: MediaDeviceKindEnum): Array<JSX.Element> {
|
||||
return devices.map((d) => {
|
||||
return (<option key={`${category}-${d.deviceId}`} value={d.deviceId}>{d.label}</option>);
|
||||
});
|
||||
}
|
||||
|
||||
private renderDropdown(kind: MediaDeviceKindEnum, label: string): JSX.Element {
|
||||
const devices = this.state.mediaDevices[kind].slice(0);
|
||||
if (devices.length === 0) return null;
|
||||
|
||||
const defaultDevice = getDefaultDevice(devices);
|
||||
return (
|
||||
<Field
|
||||
element="select"
|
||||
label={label}
|
||||
value={this.state[kind] || defaultDevice}
|
||||
onChange={(e) => this.setDevice(e.target.value, kind)}
|
||||
>
|
||||
{ this.renderDeviceOptions(devices, kind) }
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
let requestButton = null;
|
||||
let speakerDropdown = null;
|
||||
let microphoneDropdown = null;
|
||||
let webcamDropdown = null;
|
||||
if (!this.state.mediaDevices) {
|
||||
requestButton = (
|
||||
<div className='mx_VoiceUserSettingsTab_missingMediaPermissions'>
|
||||
<p>{_t("Missing media permissions, click the button below to request.")}</p>
|
||||
<AccessibleButton onClick={this.requestMediaPermissions} kind="primary">
|
||||
{_t("Request media permissions")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.mediaDevices) {
|
||||
speakerDropdown = (
|
||||
this.renderDropdown(MediaDeviceKindEnum.AudioOutput, _t("Audio Output")) ||
|
||||
<p>{ _t('No Audio Outputs detected') }</p>
|
||||
);
|
||||
microphoneDropdown = (
|
||||
this.renderDropdown(MediaDeviceKindEnum.AudioInput, _t("Microphone")) ||
|
||||
<p>{ _t('No Microphones detected') }</p>
|
||||
);
|
||||
webcamDropdown = (
|
||||
this.renderDropdown(MediaDeviceKindEnum.VideoInput, _t("Camera")) ||
|
||||
<p>{ _t('No Webcams detected') }</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_VoiceUserSettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Voice & Video")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
{ requestButton }
|
||||
{ speakerDropdown }
|
||||
{ microphoneDropdown }
|
||||
{ webcamDropdown }
|
||||
<SettingsFlag name='VideoView.flipVideoHorizontally' level={SettingLevel.ACCOUNT} />
|
||||
<SettingsFlag
|
||||
name='webRtcAllowPeerToPeer'
|
||||
level={SettingLevel.DEVICE}
|
||||
onChange={this.changeWebRtcMethod}
|
||||
/>
|
||||
<SettingsFlag
|
||||
name='fallbackICEServerAllowed'
|
||||
level={SettingLevel.DEVICE}
|
||||
onChange={this.changeFallbackICEServerAllowed}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue