Merge pull request #5062 from matrix-org/travis/settings/ts

Convert SettingsStore to TypeScript
This commit is contained in:
Travis Ralston 2020-07-31 11:07:07 -06:00 committed by GitHub
commit 3443761007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 569 additions and 452 deletions

View file

@ -25,6 +25,7 @@ import { PlatformPeg } from "../PlatformPeg";
import RoomListLayoutStore from "../stores/room-list/RoomListLayoutStore";
import {IntegrationManagers} from "../integrations/IntegrationManagers";
import {ModalManager} from "../Modal";
import SettingsStore from "../settings/SettingsStore";
declare global {
interface Window {
@ -43,6 +44,7 @@ declare global {
mxPlatformPeg: PlatformPeg;
mxIntegrationManagers: typeof IntegrationManagers;
singletonModalManager: ModalManager;
mxSettingsStore: SettingsStore;
}
// workaround for https://github.com/microsoft/TypeScript/issues/30933

View file

@ -62,10 +62,11 @@ import Matrix from 'matrix-js-sdk';
import dis from './dispatcher/dispatcher';
import WidgetUtils from './utils/WidgetUtils';
import WidgetEchoStore from './stores/WidgetEchoStore';
import SettingsStore, { SettingLevel } from './settings/SettingsStore';
import SettingsStore from './settings/SettingsStore';
import {generateHumanReadableId} from "./utils/NamingUtils";
import {Jitsi} from "./widgets/Jitsi";
import {WidgetType} from "./widgets/WidgetType";
import {SettingLevel} from "./settings/SettingLevel";
global.mxCalls = {
//room_id: MatrixCall

View file

@ -15,7 +15,8 @@
*/
import * as Matrix from 'matrix-js-sdk';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
import SettingsStore from "./settings/SettingsStore";
import {SettingLevel} from "./settings/SettingLevel";
export default {
hasAnyLabeledDevices: async function() {

View file

@ -256,7 +256,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
deviceId: creds.deviceId,
pickleKey: creds.pickleKey,
timelineSupport: true,
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer', false),
forceTURN: !SettingsStore.getValue('webRtcAllowPeerToPeer'),
fallbackICEServerAllowed: !!SettingsStore.getValue('fallbackICEServerAllowed'),
verificationMethods: [
verificationMethods.SAS,

View file

@ -27,10 +27,11 @@ import dis from './dispatcher/dispatcher';
import * as sdk from './index';
import { _t } from './languageHandler';
import Modal from './Modal';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
import SettingsStore from "./settings/SettingsStore";
import {
hideToast as hideNotificationsToast,
} from "./toasts/DesktopNotificationsToast";
import {SettingLevel} from "./settings/SettingLevel";
/*
* Dispatches:

View file

@ -20,9 +20,10 @@ import PropTypes from 'prop-types';
import dis from "../../../../dispatcher/dispatcher";
import { _t } from '../../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
import SettingsStore from "../../../../settings/SettingsStore";
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {Action} from "../../../../dispatcher/actions";
import {SettingLevel} from "../../../../settings/SettingLevel";
/*
* Allows the user to disable the Event Index.

View file

@ -19,11 +19,12 @@ import * as sdk from '../../../../index';
import PropTypes from 'prop-types';
import { _t } from '../../../../languageHandler';
import SdkConfig from '../../../../SdkConfig';
import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
import SettingsStore from "../../../../settings/SettingsStore";
import Modal from '../../../../Modal';
import {formatBytes, formatCountLong} from "../../../../utils/FormattingUtils";
import EventIndexPeg from "../../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../../settings/SettingLevel";
/*
* Allows the user to introspect the event index state and disable it.

View file

@ -51,7 +51,7 @@ import { getHomePageUrl } from '../../utils/pages';
import createRoom from "../../createRoom";
import {_t, _td, getCurrentLanguage} from '../../languageHandler';
import SettingsStore, { SettingLevel } from "../../settings/SettingsStore";
import SettingsStore from "../../settings/SettingsStore";
import ThemeController from "../../settings/controllers/ThemeController";
import { startAnyRegistrationFlow } from "../../Registration.js";
import { messageForSyncError } from '../../utils/ErrorUtils';
@ -75,6 +75,7 @@ import {showToast as showNotificationsToast} from "../../toasts/DesktopNotificat
import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import ErrorDialog from "../views/dialogs/ErrorDialog";
import { RoomNotificationStateStore } from "../../stores/notifications/RoomNotificationStateStore";
import { SettingLevel } from "../../settings/SettingLevel";
/** constants for MatrixChat.state.view */
export enum Views {

View file

@ -48,7 +48,7 @@ import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
import WidgetEchoStore from '../../stores/WidgetEchoStore';
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import SettingsStore from "../../settings/SettingsStore";
import AccessibleButton from "../views/elements/AccessibleButton";
import RightPanelStore from "../../stores/RightPanelStore";
import {haveTileForEvent} from "../views/rooms/EventTile";
@ -56,6 +56,7 @@ import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
import {Action} from "../../dispatcher/actions";
import {SettingLevel} from "../../settings/SettingLevel";
const DEBUG = false;
let debuglog = function() {};

View file

@ -26,7 +26,7 @@ import { OpenToTabPayload } from "../../dispatcher/payloads/OpenToTabPayload";
import RedesignFeedbackDialog from "../views/dialogs/RedesignFeedbackDialog";
import Modal from "../../Modal";
import LogoutDialog from "../views/dialogs/LogoutDialog";
import SettingsStore, {SettingLevel} from "../../settings/SettingsStore";
import SettingsStore from "../../settings/SettingsStore";
import {getCustomTheme} from "../../theme";
import {getHostingLink} from "../../utils/HostingLink";
import {ButtonEvent} from "../views/elements/AccessibleButton";
@ -37,6 +37,7 @@ import { UPDATE_EVENT } from "../../stores/AsyncStore";
import BaseAvatar from '../views/avatars/BaseAvatar';
import classNames from "classnames";
import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
import { SettingLevel } from "../../settings/SettingLevel";
interface IProps {
isMinimized: boolean;

View file

@ -16,10 +16,11 @@ limitations under the License.
import SdkConfig from "../../../SdkConfig";
import {getCurrentLanguage} from "../../../languageHandler";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import PlatformPeg from "../../../PlatformPeg";
import * as sdk from '../../../index';
import React from 'react';
import {SettingLevel} from "../../../settings/SettingLevel";
function onChange(newLang) {
if (getCurrentLanguage() !== newLang) {

View file

@ -19,8 +19,8 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({
propTypes: {

View file

@ -17,10 +17,11 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../languageHandler";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import * as sdk from "../../../index";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import WidgetUtils from "../../../utils/WidgetUtils";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class WidgetOpenIDPermissionsDialog extends React.Component {
static propTypes = {

View file

@ -35,12 +35,13 @@ import dis from '../../../dispatcher/dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import {aboveLeftOf, ContextMenu, ContextMenuButton} from "../../structures/ContextMenu";
import PersistedElement from "./PersistedElement";
import {WidgetType} from "../../../widgets/WidgetType";
import {Capability} from "../../../widgets/WidgetApi";
import {sleep} from "../../../utils/promise";
import {SettingLevel} from "../../../settings/SettingLevel";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false;

View file

@ -15,8 +15,9 @@ limitations under the License.
*/
import React from 'react';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import Draggable, {ILocationState} from './Draggable';
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps {
// Current room

View file

@ -20,11 +20,12 @@ import SettingsStore from "../../../settings/SettingsStore";
import { _t } from '../../../languageHandler';
import ToggleSwitch from "./ToggleSwitch";
import StyledCheckbox from "./StyledCheckbox";
import { SettingLevel } from "../../../settings/SettingLevel";
interface IProps {
// The setting must be a boolean
name: string;
level: string;
level: SettingLevel;
roomId?: string; // for per-room settings
label?: string; // untranslated
isExplicit?: boolean;
@ -52,8 +53,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
};
}
private onChange = (checked: boolean): void => {
this.save(checked);
private onChange = async (checked: boolean) => {
await this.save(checked);
this.setState({ value: checked });
if (this.props.onChange) this.props.onChange(checked);
};
@ -62,8 +63,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
this.onChange(e.target.checked);
};
private save = (val?: boolean): void => {
return SettingsStore.setValue(
private save = async (val?: boolean) => {
await SettingsStore.setValue(
this.props.name,
this.props.roomId,
this.props.level,

View file

@ -20,7 +20,8 @@ import createReactClass from 'create-react-class';
import Tinter from '../../../Tinter';
import dis from '../../../dispatcher/dispatcher';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
const ROOM_COLORS = [
// magic room default values courtesy of Ribot

View file

@ -22,10 +22,11 @@ import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
import * as sdk from "../../../index";
import { _t, _td } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import dis from "../../../dispatcher/dispatcher";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import {Action} from "../../../dispatcher/actions";
import {SettingLevel} from "../../../settings/SettingLevel";
export default createReactClass({

View file

@ -21,7 +21,8 @@ import * as sdk from "../../../index";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class RoomRecoveryReminder extends React.PureComponent {
static propTypes = {

View file

@ -18,7 +18,7 @@ import React from 'react';
import * as sdk from '../../../index';
import {_t} from "../../../languageHandler";
import {SettingLevel} from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
const SETTING_MANUALLY_VERIFY_ALL_SESSIONS = "e2ee.manuallyVerifyAllSessions";

View file

@ -20,10 +20,11 @@ import { _t } from '../../../languageHandler';
import SdkConfig from "../../../SdkConfig";
import * as sdk from '../../../index';
import Modal from '../../../Modal';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton from "../elements/AccessibleButton";
import {formatBytes, formatCountLong} from "../../../utils/FormattingUtils";
import EventIndexPeg from "../../../indexing/EventIndexPeg";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class EventIndexPanel extends React.Component {
constructor() {

View file

@ -20,7 +20,7 @@ import createReactClass from 'create-react-class';
import * as sdk from '../../../index';
import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg';
import SettingsStore, {SettingLevel} from '../../../settings/SettingsStore';
import SettingsStore from '../../../settings/SettingsStore';
import Modal from '../../../Modal';
import {
NotificationUtils,
@ -31,6 +31,7 @@ import {
import SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
import AccessibleButton from "../elements/AccessibleButton";
import {SettingLevel} from "../../../settings/SettingLevel";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.

View file

@ -18,7 +18,8 @@ import React from 'react';
import {_t} from "../../../languageHandler";
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import * as sdk from '../../../index';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import SettingsStore from "../../../settings/SettingsStore";
import {SettingLevel} from "../../../settings/SettingLevel";
export default class SetIntegrationManager extends React.Component {
constructor() {

View file

@ -21,7 +21,7 @@ import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import AccessibleButton from "../../../elements/AccessibleButton";
import Notifier from "../../../../../Notifier";
import SettingsStore from '../../../../../settings/SettingsStore';
import { SettingLevel } from '../../../../../settings/SettingsStore';
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class NotificationsSettingsTab extends React.Component {
static propTypes = {

View file

@ -20,9 +20,9 @@ import {_t} from "../../../../../languageHandler";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../..";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import Modal from "../../../../../Modal";
import QuestionDialog from "../../../dialogs/QuestionDialog";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class SecurityRoomSettingsTab extends React.Component {
static propTypes = {

View file

@ -18,7 +18,7 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
import { enumerateThemes } from "../../../../../theme";
import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher";
import Slider from "../../../elements/Slider";
@ -35,6 +35,7 @@ import Field from '../../../elements/Field';
import EventTilePreview from '../../../elements/EventTilePreview';
import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import classNames from 'classnames';
import { SettingLevel } from "../../../../../settings/SettingLevel";
interface IProps {
}

View file

@ -20,7 +20,6 @@ import React from 'react';
import {_t} from "../../../../../languageHandler";
import ProfileSettings from "../../ProfileSettings";
import * as languageHandler from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton";
@ -37,6 +36,7 @@ import IdentityAuthClient from "../../../../../IdentityAuthClient";
import {abbreviateUrl} from "../../../../../utils/UrlUtils";
import { getThreepidsWithBindStatus } from '../../../../../boundThreepids';
import Spinner from "../../../elements/Spinner";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class GeneralUserSettingsTab extends React.Component {
static propTypes = {

View file

@ -17,9 +17,10 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import PropTypes from "prop-types";
import SettingsStore, {SettingLevel} from "../../../../../settings/SettingsStore";
import SettingsStore from "../../../../../settings/SettingsStore";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import * as sdk from "../../../../../index";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export class LabsSettingToggle extends React.Component {
static propTypes = {

View file

@ -17,12 +17,12 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
import {SettingLevel} from "../../../../../settings/SettingsStore";
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";
export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [

View file

@ -19,7 +19,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import {_t} from "../../../../../languageHandler";
import SdkConfig from "../../../../../SdkConfig";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton";
@ -29,6 +28,7 @@ import * as sdk from "../../../../..";
import {sleep} from "../../../../../utils/promise";
import dis from "../../../../../dispatcher/dispatcher";
import {privateShouldBeEncrypted} from "../../../../../createRoom";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export class IgnoredUser extends React.Component {
static propTypes = {

View file

@ -21,10 +21,10 @@ import SdkConfig from "../../../../../SdkConfig";
import CallMediaHandler from "../../../../../CallMediaHandler";
import Field from "../../../elements/Field";
import AccessibleButton from "../../../elements/AccessibleButton";
import {SettingLevel} from "../../../../../settings/SettingsStore";
import {MatrixClientPeg} from "../../../../../MatrixClientPeg";
import * as sdk from "../../../../../index";
import Modal from "../../../../../Modal";
import {SettingLevel} from "../../../../../settings/SettingLevel";
export default class VoiceUserSettingsTab extends React.Component {
constructor() {

View file

@ -15,8 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import SettingsStore from "../settings/SettingsStore";
import {orderBy} from "lodash";
import { SettingLevel } from "../settings/SettingLevel";
interface ILegacyFormat {
[emoji: string]: [number, number]; // [count, date]

View file

@ -18,8 +18,9 @@ import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg";
import {EventTimeline, RoomMember} from 'matrix-js-sdk';
import {sleep} from "../utils/promise";
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import SettingsStore from "../settings/SettingsStore";
import {EventEmitter} from "events";
import {SettingLevel} from "../settings/SettingLevel";
/*
* Event indexing class that wraps the platform specific event indexing.

View file

@ -21,7 +21,8 @@ limitations under the License.
import PlatformPeg from "../PlatformPeg";
import EventIndex from "../indexing/EventIndex";
import SettingsStore, {SettingLevel} from '../settings/SettingsStore';
import SettingsStore from '../settings/SettingsStore';
import {SettingLevel} from "../settings/SettingLevel";
const INDEX_VERSION = 1;

View file

@ -21,11 +21,12 @@ import request from 'browser-request';
import counterpart from 'counterpart';
import React from 'react';
import SettingsStore, {SettingLevel} from "./settings/SettingsStore";
import SettingsStore from "./settings/SettingsStore";
import PlatformPeg from "./PlatformPeg";
// @ts-ignore - $webapp is a webpack resolve alias pointing to the output directory, see webpack config
import webpackLangJsonUrl from "$webapp/i18n/languages.json";
import { SettingLevel } from "./settings/SettingLevel";
const i18nFolder = 'i18n/';

View file

@ -16,9 +16,10 @@ limitations under the License.
import {MatrixClientPeg} from "../MatrixClientPeg";
import {ALL_RULE_TYPES, BanList} from "./BanList";
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import SettingsStore from "../settings/SettingsStore";
import {_t} from "../languageHandler";
import dis from "../dispatcher/dispatcher";
import {SettingLevel} from "../settings/SettingLevel";
// TODO: Move this and related files to the js-sdk or something once finalized.

View file

@ -141,7 +141,7 @@ export default async function sendBugReport(bugReportEndpoint: string, opts: IOp
}
// add labs options
const enabledLabs = SettingsStore.getLabsFeatures().filter(SettingsStore.isFeatureEnabled);
const enabledLabs = SettingsStore.getLabsFeatures().filter(f => SettingsStore.isFeatureEnabled(f));
if (enabledLabs.length) {
body.append('enabled_labs', enabledLabs.join(', '));
}

View file

@ -0,0 +1,29 @@
/*
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.
*/
/**
* Represents the various setting levels supported by the SettingsStore.
*/
export enum SettingLevel {
// TODO: [TS] Follow naming convention
DEVICE = "device",
ROOM_DEVICE = "room-device",
ROOM_ACCOUNT = "room-account",
ACCOUNT = "account",
ROOM = "room",
CONFIG = "config",
DEFAULT = "default",
}

View file

@ -1,7 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2018, 2019 New Vector Ltd.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2018, 2019, 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.
@ -16,9 +15,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClient} from 'matrix-js-sdk';
import { MatrixClient } from 'matrix-js-sdk/src/client';
import {_td} from '../languageHandler';
import { _td } from '../languageHandler';
import {
AudioNotificationsEnabledController,
NotificationBodyEnabledController,
@ -28,75 +27,89 @@ import CustomStatusController from "./controllers/CustomStatusController";
import ThemeController from './controllers/ThemeController';
import PushToMatrixClientController from './controllers/PushToMatrixClientController';
import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
import {RightPanelPhases} from "../stores/RightPanelStorePhases";
import FontSizeController from './controllers/FontSizeController';
import SystemFontController from './controllers/SystemFontController';
import UseSystemFontController from './controllers/UseSystemFontController';
import { SettingLevel } from "./SettingLevel";
import SettingController from "./controllers/SettingController";
import { RightPanelPhases } from "../stores/RightPanelStorePhases";
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
const LEVELS_ROOM_SETTINGS = ['device', 'room-device', 'room-account', 'account', 'config'];
const LEVELS_ROOM_OR_ACCOUNT = ['room-account', 'account'];
const LEVELS_ROOM_SETTINGS_WITH_ROOM = ['device', 'room-device', 'room-account', 'account', 'config', 'room'];
const LEVELS_ACCOUNT_SETTINGS = ['device', 'account', 'config'];
const LEVELS_FEATURE = ['device', 'config'];
const LEVELS_DEVICE_ONLY_SETTINGS = ['device'];
const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = ['device', 'config'];
const LEVELS_ROOM_SETTINGS = [
SettingLevel.DEVICE,
SettingLevel.ROOM_DEVICE,
SettingLevel.ROOM_ACCOUNT,
SettingLevel.ACCOUNT,
SettingLevel.CONFIG,
];
const LEVELS_ROOM_OR_ACCOUNT = [
SettingLevel.ROOM_ACCOUNT,
SettingLevel.ACCOUNT,
];
const LEVELS_ROOM_SETTINGS_WITH_ROOM = [
SettingLevel.DEVICE,
SettingLevel.ROOM_DEVICE,
SettingLevel.ROOM_ACCOUNT,
SettingLevel.ACCOUNT,
SettingLevel.CONFIG,
SettingLevel.ROOM,
];
const LEVELS_ACCOUNT_SETTINGS = [
SettingLevel.DEVICE,
SettingLevel.ACCOUNT,
SettingLevel.CONFIG,
];
const LEVELS_FEATURE = [
SettingLevel.DEVICE,
SettingLevel.CONFIG,
];
const LEVELS_DEVICE_ONLY_SETTINGS = [
SettingLevel.DEVICE,
];
const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = [
SettingLevel.DEVICE,
SettingLevel.CONFIG,
];
export const SETTINGS = {
// EXAMPLE SETTING:
// "my-setting": {
// // Must be set to true for features. Default is 'false'.
// isFeature: false,
//
// // Display names are strongly recommended for clarity.
// displayName: _td("Cool Name"),
//
// // Display name can also be an object for different levels.
// //displayName: {
// // "device": _td("Name for when the setting is used at 'device'"),
// // "room": _td("Name for when the setting is used at 'room'"),
// // "default": _td("The name for all other levels"),
// //}
//
// // The supported levels are required. Preferably, use the preset arrays
// // at the top of this file to define this rather than a custom array.
// supportedLevels: [
// // The order does not matter.
//
// "device", // Affects the current device only
// "room-device", // Affects the current room on the current device
// "room-account", // Affects the current room for the current account
// "account", // Affects the current account
// "room", // Affects the current room (controlled by room admins)
// "config", // Affects the current application
//
// // "default" is always supported and does not get listed here.
// ],
//
// // Required. Can be any data type. The value specified here should match
// // the data being stored (ie: if a boolean is used, the setting should
// // represent a boolean).
// default: {
// your: "value",
// },
//
// // Optional settings controller. See SettingsController for more information.
// controller: new MySettingController(),
//
// // Optional flag to make supportedLevels be respected as the order to handle
// // settings. The first element is treated as "most preferred". The "default"
// // level is always appended to the end.
// supportedLevelsAreOrdered: false,
//
// // Optional value to invert a boolean setting's value. The string given will
// // be read as the setting's ID instead of the one provided as the key for the
// // setting definition. By setting this, the returned value will automatically
// // be inverted, except for when the default value is returned. Inversion will
// // occur after the controller is asked for an override. This should be used by
// // historical settings which we don't want existing user's values be wiped. Do
// // not use this for new settings.
// invertedSettingName: "my-negative-setting",
// },
export interface ISetting {
// Must be set to true for features. Default is 'false'.
isFeature?: boolean;
// Display names are strongly recommended for clarity.
// Display name can also be an object for different levels.
displayName?: string | {
// @ts-ignore - TS wants the key to be a string, but we know better
[level: SettingLevel]: string;
};
// The supported levels are required. Preferably, use the preset arrays
// at the top of this file to define this rather than a custom array.
supportedLevels?: SettingLevel[];
// Required. Can be any data type. The value specified here should match
// the data being stored (ie: if a boolean is used, the setting should
// represent a boolean).
default: any;
// Optional settings controller. See SettingsController for more information.
controller?: SettingController;
// Optional flag to make supportedLevels be respected as the order to handle
// settings. The first element is treated as "most preferred". The "default"
// level is always appended to the end.
supportedLevelsAreOrdered?: boolean;
// Optional value to invert a boolean setting's value. The string given will
// be read as the setting's ID instead of the one provided as the key for the
// setting definition. By setting this, the returned value will automatically
// be inverted, except for when the default value is returned. Inversion will
// occur after the controller is asked for an override. This should be used by
// historical settings which we don't want existing user's values be wiped. Do
// not use this for new settings.
invertedSettingName?: string;
}
export const SETTINGS: {[setting: string]: ISetting} = {
"feature_new_spinner": {
isFeature: true,
displayName: _td("New spinner design"),
@ -153,11 +166,11 @@ export const SETTINGS = {
default: false,
},
"mjolnirRooms": {
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"mjolnirPersonalRoom": {
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: null,
},
"feature_bridge_state": {
@ -354,24 +367,24 @@ export const SETTINGS = {
},
"breadcrumb_rooms": {
// not really a setting
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"recent_emoji": {
// not really a setting
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"room_directory_servers": {
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: [],
},
"integrationProvisioning": {
supportedLevels: ['account'],
supportedLevels: [SettingLevel.ACCOUNT],
default: true,
},
"allowedWidgets": {
supportedLevels: ['room-account'],
supportedLevels: [SettingLevel.ROOM_ACCOUNT],
default: {}, // none allowed
},
"analyticsOptIn": {
@ -398,7 +411,7 @@ export const SETTINGS = {
"blacklistUnverifiedDevices": {
// We specifically want to have room-device > device so that users may set a device default
// with a per-room override.
supportedLevels: ['room-device', 'device'],
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
supportedLevelsAreOrdered: true,
displayName: {
"default": _td('Never send encrypted messages to unverified sessions from this session'),
@ -416,7 +429,7 @@ export const SETTINGS = {
default: true,
},
"urlPreviewsEnabled_e2ee": {
supportedLevels: ['room-device', 'room-account'],
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
displayName: {
"room-account": _td("Enable URL previews for this room (only affects you)"),
},
@ -455,7 +468,7 @@ export const SETTINGS = {
default: false,
},
"PinnedEvents.isOpen": {
supportedLevels: ['room-device'],
supportedLevels: [SettingLevel.ROOM_DEVICE],
default: false,
},
"promptBeforeInviteUnknownUsers": {
@ -565,7 +578,8 @@ export const SETTINGS = {
"ircDisplayNameWidth": {
// We specifically want to have room-device > device so that users may set a device default
// with a per-room override.
supportedLevels: ['room-device', 'device'],
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.DEVICE],
supportedLevelsAreOrdered: true,
displayName: _td("IRC display name width"),
default: 80,
},

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -22,27 +22,14 @@ import RoomAccountSettingsHandler from "./handlers/RoomAccountSettingsHandler";
import AccountSettingsHandler from "./handlers/AccountSettingsHandler";
import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
import {_t} from '../languageHandler';
import { _t } from '../languageHandler';
import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher';
import {SETTINGS} from "./Settings";
import { ISetting, SETTINGS } from "./Settings";
import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
import {WatchManager} from "./WatchManager";
/**
* Represents the various setting levels supported by the SettingsStore.
*/
export const SettingLevel = {
// Note: This enum is not used in this class or in the Settings file
// This should always be used elsewhere in the project.
DEVICE: "device",
ROOM_DEVICE: "room-device",
ROOM_ACCOUNT: "room-account",
ACCOUNT: "account",
ROOM: "room",
CONFIG: "config",
DEFAULT: "default",
};
import { WatchManager } from "./WatchManager";
import { SettingLevel } from "./SettingLevel";
import SettingsHandler from "./handlers/SettingsHandler";
const defaultWatchManager = new WatchManager();
@ -61,13 +48,13 @@ for (const key of Object.keys(SETTINGS)) {
}
const LEVEL_HANDLERS = {
"device": new DeviceSettingsHandler(featureNames, defaultWatchManager),
"room-device": new RoomDeviceSettingsHandler(defaultWatchManager),
"room-account": new RoomAccountSettingsHandler(defaultWatchManager),
"account": new AccountSettingsHandler(defaultWatchManager),
"room": new RoomSettingsHandler(defaultWatchManager),
"config": new ConfigSettingsHandler(),
"default": new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
[SettingLevel.DEVICE]: new DeviceSettingsHandler(featureNames, defaultWatchManager),
[SettingLevel.ROOM_DEVICE]: new RoomDeviceSettingsHandler(defaultWatchManager),
[SettingLevel.ROOM_ACCOUNT]: new RoomAccountSettingsHandler(defaultWatchManager),
[SettingLevel.ACCOUNT]: new AccountSettingsHandler(defaultWatchManager),
[SettingLevel.ROOM]: new RoomSettingsHandler(defaultWatchManager),
[SettingLevel.CONFIG]: new ConfigSettingsHandler(),
[SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
};
// Wrap all the handlers with local echo
@ -76,20 +63,41 @@ for (const key of Object.keys(LEVEL_HANDLERS)) {
}
const LEVEL_ORDER = [
'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default',
SettingLevel.DEVICE,
SettingLevel.ROOM_DEVICE,
SettingLevel.ROOM_ACCOUNT,
SettingLevel.ACCOUNT,
SettingLevel.ROOM,
SettingLevel.CONFIG,
SettingLevel.DEFAULT,
];
export type CallbackFn = (
settingName: string,
roomId: string,
atLevel: SettingLevel,
newValAtLevel: any,
newVal: any,
) => void;
interface IHandlerMap {
// @ts-ignore - TS wants this to be a string key but we know better
[level: SettingLevel]: SettingsHandler;
}
export type LabsFeatureState = "labs" | "disable" | "enable" | string;
/**
* Controls and manages application settings by providing varying levels at which the
* setting value may be specified. The levels are then used to determine what the setting
* value should be given a set of circumstances. The levels, in priority order, are:
* - "device" - Values are determined by the current device
* - "room-device" - Values are determined by the current device for a particular room
* - "room-account" - Values are determined by the current account for a particular room
* - "account" - Values are determined by the current account
* - "room" - Values are determined by a particular room (by the room admins)
* - "config" - Values are determined by the config.json
* - "default" - Values are determined by the hardcoded defaults
* - SettingLevel.DEVICE - Values are determined by the current device
* - SettingLevel.ROOM_DEVICE - Values are determined by the current device for a particular room
* - SettingLevel.ROOM_ACCOUNT - Values are determined by the current account for a particular room
* - SettingLevel.ACCOUNT - Values are determined by the current account
* - SettingLevel.ROOM - Values are determined by a particular room (by the room admins)
* - SettingLevel.CONFIG - Values are determined by the config.json
* - SettingLevel.DEFAULT - Values are determined by the hardcoded defaults
*
* Each level has a different method to storing the setting value. For implementation
* specific details, please see the handlers. The "config" and "default" levels are
@ -110,11 +118,11 @@ export default class SettingsStore {
// We also maintain a list of monitors which are special watchers: they cause dispatches
// when the setting changes. We track which rooms we're monitoring though to ensure we
// don't duplicate updates on the bus.
static _watchers = {}; // { callbackRef => { callbackFn } }
static _monitors = {}; // { settingName => { roomId => callbackRef } }
private static watchers = {}; // { callbackRef => { callbackFn } }
private static monitors = {}; // { settingName => { roomId => callbackRef } }
// Counter used for generation of watcher IDs
static _watcherCount = 1;
private static watcherCount = 1;
/**
* Watches for changes in a particular setting. This is done without any local echo
@ -132,7 +140,7 @@ export default class SettingsStore {
* if the change in value is worthwhile enough to react upon.
* @returns {string} A reference to the watcher that was employed.
*/
static watchSetting(settingName, roomId, callbackFn) {
public static watchSetting(settingName: string, roomId: string, callbackFn: CallbackFn): string {
const setting = SETTINGS[settingName];
const originalSettingName = settingName;
if (!setting) throw new Error(`${settingName} is not a setting`);
@ -141,14 +149,14 @@ export default class SettingsStore {
settingName = setting.invertedSettingName;
}
const watcherId = `${new Date().getTime()}_${SettingsStore._watcherCount++}_${settingName}_${roomId}`;
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${settingName}_${roomId}`;
const localizedCallback = (changedInRoomId, atLevel, newValAtLevel) => {
const newValue = SettingsStore.getValue(originalSettingName);
callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue);
};
SettingsStore._watchers[watcherId] = localizedCallback;
SettingsStore.watchers[watcherId] = localizedCallback;
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
return watcherId;
@ -160,14 +168,14 @@ export default class SettingsStore {
* @param {string} watcherReference The watcher reference (received from #watchSetting)
* to cancel.
*/
static unwatchSetting(watcherReference) {
if (!SettingsStore._watchers[watcherReference]) {
public static unwatchSetting(watcherReference: string) {
if (!SettingsStore.watchers[watcherReference]) {
console.warn(`Ending non-existent watcher ID ${watcherReference}`);
return;
}
defaultWatchManager.unwatchSetting(SettingsStore._watchers[watcherReference]);
delete SettingsStore._watchers[watcherReference];
defaultWatchManager.unwatchSetting(SettingsStore.watchers[watcherReference]);
delete SettingsStore.watchers[watcherReference];
}
/**
@ -178,13 +186,13 @@ export default class SettingsStore {
* @param {string} settingName The setting name to monitor.
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
*/
static monitorSetting(settingName, roomId) {
public static monitorSetting(settingName: string, roomId: string) {
roomId = roomId || null; // the thing wants null specifically to work, so appease it.
if (!this._monitors[settingName]) this._monitors[settingName] = {};
if (!this.monitors[settingName]) this.monitors[settingName] = {};
const registerWatcher = () => {
this._monitors[settingName][roomId] = SettingsStore.watchSetting(
this.monitors[settingName][roomId] = SettingsStore.watchSetting(
settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => {
dis.dispatch({
action: 'setting_updated',
@ -198,16 +206,16 @@ export default class SettingsStore {
);
};
const hasRoom = Object.keys(this._monitors[settingName]).find((r) => r === roomId || r === null);
const hasRoom = Object.keys(this.monitors[settingName]).find((r) => r === roomId || r === null);
if (!hasRoom) {
registerWatcher();
} else {
if (roomId === null) {
// Unregister all existing watchers and register the new one
for (const roomId of Object.keys(this._monitors[settingName])) {
SettingsStore.unwatchSetting(this._monitors[settingName][roomId]);
for (const roomId of Object.keys(this.monitors[settingName])) {
SettingsStore.unwatchSetting(this.monitors[settingName][roomId]);
}
this._monitors[settingName] = {};
this.monitors[settingName] = {};
registerWatcher();
} // else a watcher is already registered for the room, so don't bother registering it again
}
@ -216,11 +224,11 @@ export default class SettingsStore {
/**
* Gets the translated display name for a given setting
* @param {string} settingName The setting to look up.
* @param {"device"|"room-device"|"room-account"|"account"|"room"|"config"|"default"} atLevel
* @param {SettingLevel} atLevel
* The level to get the display name for; Defaults to 'default'.
* @return {String} The display name for the setting, or null if not found.
*/
static getDisplayName(settingName, atLevel = "default") {
public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT) {
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
let displayName = SETTINGS[settingName].displayName;
@ -229,20 +237,20 @@ export default class SettingsStore {
else displayName = displayName["default"];
}
return _t(displayName);
return _t(displayName as string);
}
/**
* Returns a list of all available labs feature names
* @returns {string[]} The list of available feature names
*/
static getLabsFeatures() {
public static getLabsFeatures(): string[] {
const possibleFeatures = Object.keys(SETTINGS).filter((s) => SettingsStore.isFeature(s));
const enableLabs = SdkConfig.get()["enableLabs"];
if (enableLabs) return possibleFeatures;
return possibleFeatures.filter((s) => SettingsStore._getFeatureState(s) === "labs");
return possibleFeatures.filter((s) => SettingsStore.getFeatureState(s) === "labs");
}
/**
@ -250,7 +258,7 @@ export default class SettingsStore {
* @param {string} settingName The setting to look up.
* @return {boolean} True if the setting is a feature.
*/
static isFeature(settingName) {
public static isFeature(settingName: string) {
if (!SETTINGS[settingName]) return false;
return SETTINGS[settingName].isFeature;
}
@ -262,7 +270,7 @@ export default class SettingsStore {
* @param {String} roomId The optional room ID to validate in, may be null.
* @return {boolean} True if the feature is enabled, false otherwise
*/
static isFeatureEnabled(settingName, roomId = null) {
public static isFeatureEnabled(settingName: string, roomId: string = null) {
if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature");
}
@ -276,7 +284,7 @@ export default class SettingsStore {
* @param {boolean} value True to enable the feature, false otherwise.
* @returns {Promise} Resolves when the setting has been set.
*/
static setFeatureEnabled(settingName, value) {
public static setFeatureEnabled(settingName: string, value: any): Promise<void> {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@ -285,7 +293,7 @@ export default class SettingsStore {
throw new Error("Setting " + settingName + " is not a feature");
}
return SettingsStore.setValue(settingName, null, "device", value);
return SettingsStore.setValue(settingName, null, SettingLevel.DEVICE, value);
}
/**
@ -296,7 +304,7 @@ export default class SettingsStore {
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found
*/
static getValue(settingName, roomId = null, excludeDefault = false) {
public static getValue(settingName: string, roomId: string = null, excludeDefault = false): any {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@ -310,7 +318,7 @@ export default class SettingsStore {
/**
* Gets a setting's value at a particular level, ignoring all levels that are more specific.
* @param {"device"|"room-device"|"room-account"|"account"|"room"|"config"|"default"} level The
* @param {SettingLevel|"config"|"default"} level The
* level to look at.
* @param {string} settingName The name of the setting to read.
* @param {String} roomId The room ID to read the setting value in, may be null.
@ -319,7 +327,13 @@ export default class SettingsStore {
* @param {boolean} excludeDefault True to disable using the default value.
* @return {*} The value, or null if not found.
*/
static getValueAt(level, settingName, roomId = null, explicit = false, excludeDefault = false) {
public static getValueAt(
level: SettingLevel,
settingName: string,
roomId: string = null,
explicit = false,
excludeDefault = false,
): any {
// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
@ -327,19 +341,19 @@ export default class SettingsStore {
}
const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER);
if (!levelOrder.includes("default")) levelOrder.push("default"); // always include default
if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default
const minIndex = levelOrder.indexOf(level);
if (minIndex === -1) throw new Error("Level " + level + " is not prioritized");
if (SettingsStore.isFeature(settingName)) {
const configValue = SettingsStore._getFeatureState(settingName);
const configValue = SettingsStore.getFeatureState(settingName);
if (configValue === "enable") return true;
if (configValue === "disable") return false;
// else let it fall through the default process
}
const handlers = SettingsStore._getHandlers(settingName);
const handlers = SettingsStore.getHandlers(settingName);
// Check if we need to invert the setting at all. Do this after we get the setting
// handlers though, otherwise we'll fail to read the value.
@ -351,10 +365,10 @@ export default class SettingsStore {
if (explicit) {
const handler = handlers[level];
if (!handler) {
return SettingsStore._getFinalValue(setting, level, roomId, null, null);
return SettingsStore.getFinalValue(setting, level, roomId, null, null);
}
const value = handler.getValue(settingName, roomId);
return SettingsStore._getFinalValue(setting, level, roomId, value, level);
return SettingsStore.getFinalValue(setting, level, roomId, value, level);
}
for (let i = minIndex; i < levelOrder.length; i++) {
@ -364,10 +378,10 @@ export default class SettingsStore {
const value = handler.getValue(settingName, roomId);
if (value === null || value === undefined) continue;
return SettingsStore._getFinalValue(setting, level, roomId, value, levelOrder[i]);
return SettingsStore.getFinalValue(setting, level, roomId, value, levelOrder[i]);
}
return SettingsStore._getFinalValue(setting, level, roomId, null, null);
return SettingsStore.getFinalValue(setting, level, roomId, null, null);
}
/**
@ -376,7 +390,7 @@ export default class SettingsStore {
* @param {String} roomId The room ID to read the setting value in, may be null.
* @return {*} The default value
*/
static getDefaultValue(settingName) {
public static getDefaultValue(settingName: string): any {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
@ -385,7 +399,13 @@ export default class SettingsStore {
return SETTINGS[settingName].default;
}
static _getFinalValue(setting, level, roomId, calculatedValue, calculatedAtLevel) {
private static getFinalValue(
setting: ISetting,
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
): any {
let resultingValue = calculatedValue;
if (setting.controller) {
@ -404,20 +424,21 @@ export default class SettingsStore {
* to indicate that the level should no longer have an override.
* @param {string} settingName The name of the setting to change.
* @param {String} roomId The room ID to change the value in, may be null.
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
* @param {SettingLevel} level The level
* to change the value at.
* @param {*} value The new value of the setting, may be null.
* @return {Promise} Resolves when the setting has been changed.
*/
/* eslint-enable valid-jsdoc */
static async setValue(settingName, roomId, level, value) {
public static async setValue(settingName: string, roomId: string, level: SettingLevel, value: any): Promise<void> {
// Verify that the setting is actually a setting
const setting = SETTINGS[settingName];
if (!setting) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
}
const handler = SettingsStore._getHandler(settingName, level);
const handler = SettingsStore.getHandler(settingName, level);
if (!handler) {
throw new Error("Setting " + settingName + " does not have a handler for " + level);
}
@ -449,28 +470,28 @@ export default class SettingsStore {
* set for a particular room, otherwise it should be supplied.
* @param {string} settingName The name of the setting to check.
* @param {String} roomId The room ID to check in, may be null.
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level to
* @param {SettingLevel} level The level to
* check at.
* @return {boolean} True if the user may set the setting, false otherwise.
*/
static canSetValue(settingName, roomId, level) {
public static canSetValue(settingName: string, roomId: string, level: SettingLevel): boolean {
// Verify that the setting is actually a setting
if (!SETTINGS[settingName]) {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
}
const handler = SettingsStore._getHandler(settingName, level);
const handler = SettingsStore.getHandler(settingName, level);
if (!handler) return false;
return handler.canSetValue(settingName, roomId);
}
/**
* Determines if the given level is supported on this device.
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
* @param {SettingLevel} level The level
* to check the feasibility of.
* @return {boolean} True if the level is supported, false otherwise.
*/
static isLevelSupported(level) {
public static isLevelSupported(level: SettingLevel): boolean {
if (!LEVEL_HANDLERS[level]) return false;
return LEVEL_HANDLERS[level].isSupported();
}
@ -482,7 +503,7 @@ export default class SettingsStore {
* @param {string} realSettingName The setting name to try and read.
* @param {string} roomId Optional room ID to test the setting in.
*/
static debugSetting(realSettingName, roomId) {
public static debugSetting(realSettingName: string, roomId: string) {
console.log(`--- DEBUG ${realSettingName}`);
// Note: we intentionally use JSON.stringify here to avoid the console masking the
@ -570,13 +591,13 @@ export default class SettingsStore {
console.log(`--- END DEBUG`);
}
static _getHandler(settingName, level) {
const handlers = SettingsStore._getHandlers(settingName);
private static getHandler(settingName: string, level: SettingLevel): SettingsHandler {
const handlers = SettingsStore.getHandlers(settingName);
if (!handlers[level]) return null;
return handlers[level];
}
static _getHandlers(settingName) {
private static getHandlers(settingName: string): IHandlerMap {
if (!SETTINGS[settingName]) return {};
const handlers = {};
@ -591,7 +612,7 @@ export default class SettingsStore {
return handlers;
}
static _getFeatureState(settingName) {
private static getFeatureState(settingName: string): LabsFeatureState {
const featuresConfig = SdkConfig.get()['features'];
const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag
@ -611,4 +632,4 @@ export default class SettingsStore {
}
// For debugging purposes
global.mxSettingsStore = SettingsStore;
window.mxSettingsStore = SettingsStore;

View file

@ -14,41 +14,52 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { SettingLevel } from "./SettingLevel";
export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void;
const IRRELEVANT_ROOM = Symbol("any room");
interface RoomWatcherMap {
// @ts-ignore - TS wants string-only keys but we know better - https://github.com/Microsoft/TypeScript/issues/1863
[roomId: string | symbol]: CallbackFn[];
}
/**
* Generalized management class for dealing with watchers on a per-handler (per-level)
* basis without duplicating code. Handlers are expected to push updates through this
* class, which are then proxied outwards to any applicable watchers.
*/
export class WatchManager {
_watchers = {}; // { settingName: { roomId: callbackFns[] } }
private watchers: {[settingName: string]: RoomWatcherMap} = {};
// Proxy for handlers to delegate changes to this manager
watchSetting(settingName, roomId, cb) {
if (!this._watchers[settingName]) this._watchers[settingName] = {};
if (!this._watchers[settingName][roomId]) this._watchers[settingName][roomId] = [];
this._watchers[settingName][roomId].push(cb);
public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) {
if (!this.watchers[settingName]) this.watchers[settingName] = {};
if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = [];
this.watchers[settingName][roomId].push(cb);
}
// Proxy for handlers to delegate changes to this manager
unwatchSetting(cb) {
for (const settingName of Object.keys(this._watchers)) {
for (const roomId of Object.keys(this._watchers[settingName])) {
public unwatchSetting(cb: CallbackFn) {
for (const settingName of Object.keys(this.watchers)) {
for (const roomId of Object.keys(this.watchers[settingName])) {
let idx;
while ((idx = this._watchers[settingName][roomId].indexOf(cb)) !== -1) {
this._watchers[settingName][roomId].splice(idx, 1);
while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) {
this.watchers[settingName][roomId].splice(idx, 1);
}
}
}
}
notifyUpdate(settingName, inRoomId, atLevel, newValueAtLevel) {
public notifyUpdate(settingName: string, inRoomId: string | null, atLevel: SettingLevel, newValueAtLevel: any) {
// Dev note: We could avoid raising changes for ultimately inconsequential changes, but
// we also don't have a reliable way to get the old value of a setting. Instead, we'll just
// let it fall through regardless and let the receiver dedupe if they want to.
if (!this._watchers[settingName]) return;
if (!this.watchers[settingName]) return;
const roomWatchers = this._watchers[settingName];
const roomWatchers = this.watchers[settingName];
const callbacks = [];
if (inRoomId !== null && roomWatchers[inRoomId]) {
@ -59,8 +70,8 @@ export class WatchManager {
// Fire updates to all the individual room watchers too, as they probably
// care about the change higher up.
callbacks.push(...Object.values(roomWatchers).reduce((r, a) => [...r, ...a], []));
} else if (roomWatchers[null]) {
callbacks.push(...roomWatchers[null]);
} else if (roomWatchers[IRRELEVANT_ROOM]) {
callbacks.push(...roomWatchers[IRRELEVANT_ROOM]);
}
for (const callback of callbacks) {

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019, 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.
@ -16,9 +16,10 @@ limitations under the License.
import SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher";
import { SettingLevel } from "../SettingLevel";
export default class CustomStatusController extends SettingController {
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch setting change so that some components that are still visible when the
// Settings page is open (such as RoomTiles) can reflect the change.
dis.dispatch({

View file

@ -18,13 +18,14 @@ import SettingController from "./SettingController";
import dis from "../../dispatcher/dispatcher";
import { UpdateFontSizePayload } from "../../dispatcher/payloads/UpdateFontSizePayload";
import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class FontSizeController extends SettingController {
constructor() {
super();
}
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateFontSizePayload>({
action: Action.UpdateFontSize,

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Travis Ralston
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.
@ -16,13 +17,14 @@ limitations under the License.
import SettingController from "./SettingController";
import {MatrixClientPeg} from '../../MatrixClientPeg';
import { SettingLevel } from "../SettingLevel";
// XXX: This feels wrong.
import {PushProcessor} from "matrix-js-sdk/src/pushprocessor";
// .m.rule.master being enabled means all events match that push rule
// default action on this rule is dont_notify, but it could be something else
function isPushNotifyDisabled() {
function isPushNotifyDisabled(): boolean {
// Return the value of the master push rule as a default
const processor = new PushProcessor(MatrixClientPeg.get());
const masterRule = processor.getPushRuleById(".m.rule.master");
@ -36,14 +38,20 @@ function isPushNotifyDisabled() {
return masterRule.enabled && !masterRule.actions.includes("notify");
}
function getNotifier() {
function getNotifier(): any { // TODO: [TS] Formal type that doesn't cause a cyclical reference.
// eslint-disable-next-line @typescript-eslint/no-var-requires
let Notifier = require('../../Notifier'); // avoids cyclical references
if (Notifier.default) Notifier = Notifier.default; // correct for webpack require() weirdness
return Notifier;
}
export class NotificationsEnabledController extends SettingController {
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
public getValueOverride(
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
): any {
if (!getNotifier().isPossible()) return false;
if (calculatedValue === null || calculatedAtLevel === "default") {
@ -53,7 +61,7 @@ export class NotificationsEnabledController extends SettingController {
return calculatedValue;
}
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
if (getNotifier().supportsDesktopNotifications()) {
getNotifier().setEnabled(newValue);
}
@ -61,7 +69,7 @@ export class NotificationsEnabledController extends SettingController {
}
export class NotificationBodyEnabledController extends SettingController {
getValueOverride(level, roomId, calculatedValue) {
public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any): any {
if (!getNotifier().isPossible()) return false;
if (calculatedValue === null) {
@ -73,7 +81,7 @@ export class NotificationBodyEnabledController extends SettingController {
}
export class AudioNotificationsEnabledController extends SettingController {
getValueOverride(level, roomId, calculatedValue) {
public getValueOverride(level: SettingLevel, roomId: string, calculatedValue: any): any {
if (!getNotifier().isPossible()) return false;
// Note: Audio notifications are *not* enabled by default.

View file

@ -15,23 +15,20 @@ limitations under the License.
*/
import { MatrixClientPeg } from '../../MatrixClientPeg';
import { SettingLevel } from "../SettingLevel";
import SettingController from "./SettingController";
/**
* When the value changes, call a setter function on the matrix client with the new value
*/
export default class PushToMatrixClientController {
constructor(setter, inverse) {
this._setter = setter;
this._inverse = inverse;
export default class PushToMatrixClientController extends SettingController {
constructor(private setter: Function, private inverse: boolean) {
super();
}
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
return null; // no override
}
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// XXX does this work? This surely isn't necessarily the effective value,
// but it's what NotificationsEnabledController does...
this._setter.call(MatrixClientPeg.get(), this._inverse ? !newValue : newValue);
this.setter.call(MatrixClientPeg.get(), this.inverse ? !newValue : newValue);
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Copyright 2019, 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.
@ -16,9 +16,10 @@ limitations under the License.
import SettingController from "./SettingController";
import PlatformPeg from "../../PlatformPeg";
import { SettingLevel } from "../SettingLevel";
export default class ReloadOnChangeController extends SettingController {
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
PlatformPeg.get().reload();
}
}

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Travis Ralston
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.
@ -14,6 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { SettingLevel } from "../SettingLevel";
/**
* Represents a controller for individual settings to alter the reading behaviour
* based upon environmental conditions, or to react to changes and therefore update
@ -22,7 +25,7 @@ limitations under the License.
* This is not intended to replace the functionality of a SettingsHandler, it is only
* intended to handle environmental factors for specific settings.
*/
export default class SettingController {
export default abstract class SettingController {
/**
* Gets the overridden value for the setting, if any. This must return null if the
* value is not to be overridden, otherwise it must return the new value.
@ -30,11 +33,16 @@ export default class SettingController {
* @param {String} roomId The room ID, may be null.
* @param {*} calculatedValue The value that the handlers think the setting should be,
* may be null.
* @param {string} calculatedAtLevel The level for which the calculated value was
* @param {SettingLevel} calculatedAtLevel The level for which the calculated value was
* calculated at. May be null.
* @return {*} The value that should be used, or null if no override is applicable.
*/
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
public getValueOverride(
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
): any {
return null; // no override
}
@ -44,7 +52,7 @@ export default class SettingController {
* @param {String} roomId The room ID, may be null.
* @param {*} newValue The new value for the setting, may be null.
*/
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// do nothing by default
}
}

View file

@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class SystemFontController extends SettingController {
constructor() {
super();
}
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateSystemFontPayload>({
action: Action.UpdateSystemFont,

View file

@ -1,6 +1,6 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019, 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.
@ -17,11 +17,17 @@ limitations under the License.
import SettingController from "./SettingController";
import {DEFAULT_THEME, enumerateThemes} from "../../theme";
import { SettingLevel } from "../SettingLevel";
export default class ThemeController extends SettingController {
static isLogin = false;
public static isLogin = false;
getValueOverride(level, roomId, calculatedValue, calculatedAtLevel) {
public getValueOverride(
level: SettingLevel,
roomId: string,
calculatedValue: any,
calculatedAtLevel: SettingLevel,
): any {
if (!calculatedValue) return null; // Don't override null themes
if (ThemeController.isLogin) return 'light';

View file

@ -19,13 +19,14 @@ import SettingsStore from "../SettingsStore";
import dis from "../../dispatcher/dispatcher";
import { UpdateSystemFontPayload } from "../../dispatcher/payloads/UpdateSystemFontPayload";
import { Action } from "../../dispatcher/actions";
import { SettingLevel } from "../SettingLevel";
export default class UseSystemFontController extends SettingController {
constructor() {
super();
}
onChange(level, roomId, newValue) {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// Dispatch font size change so that everything open responds to the change.
dis.dispatch<UpdateSystemFontPayload>({
action: Action.UpdateSystemFont,

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -17,14 +17,16 @@ limitations under the License.
import {MatrixClientPeg} from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore";
import {objectClone, objectKeyChanges} from "../../utils/objects";
import {SettingLevel} from "../SettingLevel";
import { WatchManager } from "../WatchManager";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
const BREADCRUMBS_LEGACY_EVENT_TYPE = "im.vector.riot.breadcrumb_rooms";
const BREADCRUMBS_EVENT_TYPE = "im.vector.setting.breadcrumbs";
const BREADCRUMBS_EVENT_TYPES = [BREADCRUMBS_LEGACY_EVENT_TYPE, BREADCRUMBS_EVENT_TYPE];
const RECENT_EMOJI_EVENT_TYPE = "io.element.recent_emoji";
const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisioning";
/**
@ -32,22 +34,19 @@ const INTEG_PROVISIONING_EVENT_TYPE = "im.vector.setting.integration_provisionin
* This handler does not make use of the roomId parameter.
*/
export default class AccountSettingsHandler extends MatrixClientBackedSettingsHandler {
constructor(watchManager) {
constructor(private watchers: WatchManager) {
super();
this._watchers = watchManager;
this._onAccountData = this._onAccountData.bind(this);
}
initMatrixClient(oldClient, newClient) {
public initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
oldClient.removeListener("accountData", this._onAccountData);
oldClient.removeListener("accountData", this.onAccountData);
}
newClient.on("accountData", this._onAccountData);
newClient.on("accountData", this.onAccountData);
}
_onAccountData(event, prevEvent) {
private onAccountData = (event: MatrixEvent, prevEvent: MatrixEvent) => {
if (event.getType() === "org.matrix.preview_urls") {
let val = event.getContent()['disable'];
if (typeof(val) !== "boolean") {
@ -56,30 +55,30 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
val = !val;
}
this._watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
this.watchers.notifyUpdate("urlPreviewsEnabled", null, SettingLevel.ACCOUNT, val);
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
const val = event.getContent()[settingName];
this._watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
this.watchers.notifyUpdate(settingName, null, SettingLevel.ACCOUNT, val);
}
} else if (BREADCRUMBS_EVENT_TYPES.includes(event.getType())) {
this._notifyBreadcrumbsUpdate(event);
this.notifyBreadcrumbsUpdate(event);
} else if (event.getType() === INTEG_PROVISIONING_EVENT_TYPE) {
const val = event.getContent()['enabled'];
this._watchers.notifyUpdate("integrationProvisioning", null, SettingLevel.ACCOUNT, val);
this.watchers.notifyUpdate("integrationProvisioning", null, SettingLevel.ACCOUNT, val);
} else if (event.getType() === RECENT_EMOJI_EVENT_TYPE) {
const val = event.getContent()['enabled'];
this._watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val);
this.watchers.notifyUpdate("recent_emoji", null, SettingLevel.ACCOUNT, val);
}
}
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings("org.matrix.preview_urls") || {};
const content = this.getSettings("org.matrix.preview_urls") || {};
// Check to make sure that we actually got a boolean
if (typeof(content['disable']) !== "boolean") return null;
@ -88,9 +87,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") {
let content = this._getSettings(BREADCRUMBS_EVENT_TYPE);
let content = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (!content || !content['recent_rooms']) {
content = this._getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
content = this.getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
// This is a bit of a hack, but it makes things slightly easier
if (content) content['recent_rooms'] = content['rooms'];
@ -101,17 +100,17 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case recent emoji
if (settingName === "recent_emoji") {
const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE);
const content = this.getSettings(RECENT_EMOJI_EVENT_TYPE);
return content ? content["recent_emoji"] : null;
}
// Special case integration manager provisioning
if (settingName === "integrationProvisioning") {
const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE);
const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE);
return content ? content['enabled'] : null;
}
const settings = this._getSettings() || {};
const settings = this.getSettings() || {};
let preferredValue = settings[settingName];
if (preferredValue === null || preferredValue === undefined) {
@ -124,10 +123,10 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return preferredValue;
}
setValue(settingName, roomId, newValue) {
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings("org.matrix.preview_urls") || {};
const content = this.getSettings("org.matrix.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", content);
}
@ -135,9 +134,9 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case for breadcrumbs
if (settingName === "breadcrumb_rooms") {
// We read the value first just to make sure we preserve whatever random keys might be present.
let content = this._getSettings(BREADCRUMBS_EVENT_TYPE);
let content = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (!content || !content['recent_rooms']) {
content = this._getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
content = this.getSettings(BREADCRUMBS_LEGACY_EVENT_TYPE);
}
if (!content) content = {}; // If we still don't have content, make some
@ -147,33 +146,33 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
// Special case recent emoji
if (settingName === "recent_emoji") {
const content = this._getSettings(RECENT_EMOJI_EVENT_TYPE) || {};
const content = this.getSettings(RECENT_EMOJI_EVENT_TYPE) || {};
content["recent_emoji"] = newValue;
return MatrixClientPeg.get().setAccountData(RECENT_EMOJI_EVENT_TYPE, content);
}
// Special case integration manager provisioning
if (settingName === "integrationProvisioning") {
const content = this._getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
const content = this.getSettings(INTEG_PROVISIONING_EVENT_TYPE) || {};
content['enabled'] = newValue;
return MatrixClientPeg.get().setAccountData(INTEG_PROVISIONING_EVENT_TYPE, content);
}
const content = this._getSettings() || {};
const content = this.getSettings() || {};
content[settingName] = newValue;
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", content);
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their account, so they should be able to
}
isSupported() {
public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(eventType = "im.vector.web.settings") {
private getSettings(eventType = "im.vector.web.settings"): any { // TODO: [TS] Types on return
const cli = MatrixClientPeg.get();
if (!cli) return null;
@ -182,11 +181,11 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
return objectClone(event.getContent()); // clone to prevent mutation
}
_notifyBreadcrumbsUpdate(event) {
private notifyBreadcrumbsUpdate(event: MatrixEvent) {
let val = [];
if (event.getType() === BREADCRUMBS_LEGACY_EVENT_TYPE) {
// This seems fishy - try and get the event for the new rooms
const newType = this._getSettings(BREADCRUMBS_EVENT_TYPE);
const newType = this.getSettings(BREADCRUMBS_EVENT_TYPE);
if (newType) val = newType['recent_rooms'];
else val = event.getContent()['rooms'];
} else if (event.getType() === BREADCRUMBS_EVENT_TYPE) {
@ -194,6 +193,6 @@ export default class AccountSettingsHandler extends MatrixClientBackedSettingsHa
} else {
return; // for sanity, not because we expect to be here.
}
this._watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val || []);
this.watchers.notifyUpdate("breadcrumb_rooms", null, SettingLevel.ACCOUNT, val || []);
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd
Copyright 2019, 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.
@ -24,7 +24,7 @@ import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
* roomId parameter.
*/
export default class ConfigSettingsHandler extends SettingsHandler {
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
const config = SdkConfig.get() || {};
// Special case themes
@ -37,15 +37,15 @@ export default class ConfigSettingsHandler extends SettingsHandler {
return settingsConfig[settingName];
}
setValue(settingName, roomId, newValue) {
public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
throw new Error("Cannot change settings at the config level");
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
return false;
}
isSupported() {
public isSupported(): boolean {
return true; // SdkConfig is always there
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -27,29 +27,27 @@ export default class DefaultSettingsHandler extends SettingsHandler {
* @param {object} defaults The default setting values, keyed by setting name.
* @param {object} invertedDefaults The default inverted setting values, keyed by setting name.
*/
constructor(defaults, invertedDefaults) {
constructor(private defaults: Record<string, any>, private invertedDefaults: Record<string, any>) {
super();
this._defaults = defaults;
this._invertedDefaults = invertedDefaults;
}
getValue(settingName, roomId) {
let value = this._defaults[settingName];
public getValue(settingName: string, roomId: string): any {
let value = this.defaults[settingName];
if (value === undefined) {
value = this._invertedDefaults[settingName];
value = this.invertedDefaults[settingName];
}
return value;
}
setValue(settingName, roomId, newValue) {
public async setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
throw new Error("Cannot set values on the default level handler");
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string) {
return false;
}
isSupported() {
public isSupported(): boolean {
return true;
}
}

View file

@ -18,7 +18,8 @@ limitations under the License.
import SettingsHandler from "./SettingsHandler";
import {MatrixClientPeg} from "../../MatrixClientPeg";
import {SettingLevel} from "../SettingsStore";
import {SettingLevel} from "../SettingLevel";
import { CallbackFn, WatchManager } from "../WatchManager";
/**
* Gets and sets settings at the "device" level for the current device.
@ -29,17 +30,15 @@ export default class DeviceSettingsHandler extends SettingsHandler {
/**
* Creates a new device settings handler
* @param {string[]} featureNames The names of known features.
* @param {WatchManager} watchManager The watch manager to notify updates to
* @param {WatchManager} watchers The watch manager to notify updates to
*/
constructor(featureNames, watchManager) {
constructor(private featureNames: string[], private watchers: WatchManager) {
super();
this._featureNames = featureNames;
this._watchers = watchManager;
}
getValue(settingName, roomId) {
if (this._featureNames.includes(settingName)) {
return this._readFeature(settingName);
public getValue(settingName: string, roomId: string): any {
if (this.featureNames.includes(settingName)) {
return this.readFeature(settingName);
}
// Special case notifications
@ -68,28 +67,28 @@ export default class DeviceSettingsHandler extends SettingsHandler {
return val['value'];
}
const settings = this._getSettings() || {};
const settings = this.getSettings() || {};
return settings[settingName];
}
setValue(settingName, roomId, newValue) {
if (this._featureNames.includes(settingName)) {
this._writeFeature(settingName, newValue);
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
if (this.featureNames.includes(settingName)) {
this.writeFeature(settingName, newValue);
return Promise.resolve();
}
// Special case notifications
if (settingName === "notificationsEnabled") {
localStorage.setItem("notifications_enabled", newValue);
this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
} else if (settingName === "notificationBodyEnabled") {
localStorage.setItem("notifications_body_enabled", newValue);
this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
} else if (settingName === "audioNotificationsEnabled") {
localStorage.setItem("audio_notifications_enabled", newValue);
this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
@ -103,35 +102,35 @@ export default class DeviceSettingsHandler extends SettingsHandler {
"lastRightPanelPhaseForGroup",
].includes(settingName)) {
localStorage.setItem(`mx_${settingName}`, JSON.stringify({value: newValue}));
this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
const settings = this._getSettings() || {};
const settings = this.getSettings() || {};
settings[settingName] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(settings));
this._watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
return Promise.resolve();
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their device, so they should be able to
}
isSupported() {
public isSupported(): boolean {
return localStorage !== undefined && localStorage !== null;
}
watchSetting(settingName, roomId, cb) {
this._watchers.watchSetting(settingName, roomId, cb);
public watchSetting(settingName: string, roomId: string, cb: CallbackFn) {
this.watchers.watchSetting(settingName, roomId, cb);
}
unwatchSetting(cb) {
this._watchers.unwatchSetting(cb);
public unwatchSetting(cb: CallbackFn) {
this.watchers.unwatchSetting(cb);
}
_getSettings() {
private getSettings(): any { // TODO: [TS] Type return
const value = localStorage.getItem("mx_local_settings");
if (!value) return null;
return JSON.parse(value);
@ -140,7 +139,7 @@ export default class DeviceSettingsHandler extends SettingsHandler {
// Note: features intentionally don't use the same key as settings to avoid conflicts
// and to be backwards compatible.
_readFeature(featureName) {
private readFeature(featureName: string): boolean | null {
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest()) {
// Guests should not have any labs features enabled.
return false;
@ -153,8 +152,8 @@ export default class DeviceSettingsHandler extends SettingsHandler {
return null;
}
_writeFeature(featureName, enabled) {
localStorage.setItem("mx_labs_feature_" + featureName, enabled);
this._watchers.notifyUpdate(featureName, null, SettingLevel.DEVICE, enabled);
private writeFeature(featureName: string, enabled: boolean | null) {
localStorage.setItem("mx_labs_feature_" + featureName, `${enabled}`);
this.watchers.notifyUpdate(featureName, null, SettingLevel.DEVICE, enabled);
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -23,47 +23,48 @@ import SettingsHandler from "./SettingsHandler";
* handler as much as possible to ensure values are not stale.
*/
export default class LocalEchoWrapper extends SettingsHandler {
private cache: {
[settingName: string]: {
[roomId: string]: any;
};
} = {};
/**
* Creates a new local echo wrapper
* @param {SettingsHandler} handler The handler to wrap
*/
constructor(handler) {
constructor(private handler: SettingsHandler) {
super();
this._handler = handler;
this._cache = {
// settingName: { roomId: value }
};
}
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
const bySetting = this._cache[settingName];
const bySetting = this.cache[settingName];
if (bySetting && bySetting.hasOwnProperty(cacheRoomId)) {
return bySetting[cacheRoomId];
}
return this._handler.getValue(settingName, roomId);
return this.handler.getValue(settingName, roomId);
}
setValue(settingName, roomId, newValue) {
if (!this._cache[settingName]) this._cache[settingName] = {};
const bySetting = this._cache[settingName];
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
if (!this.cache[settingName]) this.cache[settingName] = {};
const bySetting = this.cache[settingName];
const cacheRoomId = roomId ? roomId : "UNDEFINED"; // avoid weird keys
bySetting[cacheRoomId] = newValue;
const handlerPromise = this._handler.setValue(settingName, roomId, newValue);
const handlerPromise = this.handler.setValue(settingName, roomId, newValue);
return Promise.resolve(handlerPromise).finally(() => {
delete bySetting[cacheRoomId];
});
}
canSetValue(settingName, roomId) {
return this._handler.canSetValue(settingName, roomId);
public canSetValue(settingName: string, roomId: string): boolean {
return this.handler.canSetValue(settingName, roomId);
}
isSupported() {
return this._handler.isSupported();
public isSupported(): boolean {
return this.handler.isSupported();
}
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -15,6 +15,7 @@ limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
import { MatrixClient } from "matrix-js-sdk/src/client";
// Dev note: This whole class exists in the event someone logs out and back in - we want
// to make sure the right MatrixClient is listening for changes.
@ -23,30 +24,30 @@ import SettingsHandler from "./SettingsHandler";
* Represents the base class for settings handlers which need access to a MatrixClient.
* This class performs no logic and should be overridden.
*/
export default class MatrixClientBackedSettingsHandler extends SettingsHandler {
static _matrixClient;
static _instances = [];
export default abstract class MatrixClientBackedSettingsHandler extends SettingsHandler {
private static _matrixClient: MatrixClient;
private static instances: MatrixClientBackedSettingsHandler[] = [];
static set matrixClient(client) {
public static set matrixClient(client: MatrixClient) {
const oldClient = MatrixClientBackedSettingsHandler._matrixClient;
MatrixClientBackedSettingsHandler._matrixClient = client;
for (const instance of MatrixClientBackedSettingsHandler._instances) {
for (const instance of MatrixClientBackedSettingsHandler.instances) {
instance.initMatrixClient(oldClient, client);
}
}
constructor() {
protected constructor() {
super();
MatrixClientBackedSettingsHandler._instances.push(this);
MatrixClientBackedSettingsHandler.instances.push(this);
}
get client() {
public get client(): MatrixClient {
return MatrixClientBackedSettingsHandler._matrixClient;
}
initMatrixClient() {
protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
console.warn("initMatrixClient not overridden");
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -15,10 +15,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from '../../MatrixClientPeg';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore";
import {objectClone, objectKeyChanges} from "../../utils/objects";
import { objectClone, objectKeyChanges } from "../../utils/objects";
import { SettingLevel } from "../SettingLevel";
import { WatchManager } from "../WatchManager";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
@ -26,22 +30,19 @@ const ALLOWED_WIDGETS_EVENT_TYPE = "im.vector.setting.allowed_widgets";
* Gets and sets settings at the "room-account" level for the current user.
*/
export default class RoomAccountSettingsHandler extends MatrixClientBackedSettingsHandler {
constructor(watchManager) {
constructor(private watchers: WatchManager) {
super();
this._watchers = watchManager;
this._onAccountData = this._onAccountData.bind(this);
}
initMatrixClient(oldClient, newClient) {
protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
oldClient.removeListener("Room.accountData", this._onAccountData);
oldClient.removeListener("Room.accountData", this.onAccountData);
}
newClient.on("Room.accountData", this._onAccountData);
newClient.on("Room.accountData", this.onAccountData);
}
_onAccountData(event, room, prevEvent) {
private onAccountData = (event: MatrixEvent, room: Room, prevEvent: MatrixEvent) => {
const roomId = room.roomId;
if (event.getType() === "org.matrix.room.preview_urls") {
@ -52,29 +53,29 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
val = !val;
}
this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM_ACCOUNT, val);
this.watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM_ACCOUNT, val);
} else if (event.getType() === "org.matrix.room.color_scheme") {
this._watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
this.watchers.notifyUpdate("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
const val = event.getContent()[settingName];
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_ACCOUNT, val);
}
} else if (event.getType() === ALLOWED_WIDGETS_EVENT_TYPE) {
this._watchers.notifyUpdate("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
this.watchers.notifyUpdate("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, event.getContent());
}
}
};
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
// Check to make sure that we actually got a boolean
if (typeof(content['disable']) !== "boolean") return null;
if (typeof (content['disable']) !== "boolean") return null;
return !content['disable'];
}
@ -83,22 +84,22 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
// The event content should already be in an appropriate format, we just need
// to get the right value.
// don't fallback to {} because thats truthy and would imply there is an event specifying tint
return this._getSettings(roomId, "org.matrix.room.color_scheme");
return this.getSettings(roomId, "org.matrix.room.color_scheme");
}
// Special case allowed widgets
if (settingName === "allowedWidgets") {
return this._getSettings(roomId, ALLOWED_WIDGETS_EVENT_TYPE);
return this.getSettings(roomId, ALLOWED_WIDGETS_EVENT_TYPE);
}
const settings = this._getSettings(roomId) || {};
const settings = this.getSettings(roomId) || {};
return settings[settingName];
}
setValue(settingName, roomId, newValue) {
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "org.matrix.room.preview_urls", content);
}
@ -114,24 +115,24 @@ export default class RoomAccountSettingsHandler extends MatrixClientBackedSettin
return MatrixClientPeg.get().setRoomAccountData(roomId, ALLOWED_WIDGETS_EVENT_TYPE, newValue);
}
const content = this._getSettings(roomId) || {};
const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
return MatrixClientPeg.get().setRoomAccountData(roomId, "im.vector.web.settings", content);
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
const room = MatrixClientPeg.get().getRoom(roomId);
// If they have the room, they can set their own account data
return room !== undefined && room !== null;
}
isSupported() {
public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(roomId, eventType = "im.vector.web.settings") {
private getSettings(roomId: string, eventType = "im.vector.web.settings"): any { // TODO: [TS] Type return
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return null;

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -16,71 +16,70 @@ limitations under the License.
*/
import SettingsHandler from "./SettingsHandler";
import {SettingLevel} from "../SettingsStore";
import { SettingLevel } from "../SettingLevel";
import { WatchManager } from "../WatchManager";
/**
* Gets and sets settings at the "room-device" level for the current device in a particular
* room.
*/
export default class RoomDeviceSettingsHandler extends SettingsHandler {
constructor(watchManager) {
constructor(private watchers: WatchManager) {
super();
this._watchers = watchManager;
}
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
// Special case blacklist setting to use legacy values
if (settingName === "blacklistUnverifiedDevices") {
const value = this._read("mx_local_settings");
const value = this.read("mx_local_settings");
if (value && value['blacklistUnverifiedDevicesPerRoom']) {
return value['blacklistUnverifiedDevicesPerRoom'][roomId];
}
}
const value = this._read(this._getKey(settingName, roomId));
const value = this.read(this.getKey(settingName, roomId));
if (value) return value.value;
return null;
}
setValue(settingName, roomId, newValue) {
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
// Special case blacklist setting for legacy structure
if (settingName === "blacklistUnverifiedDevices") {
let value = this._read("mx_local_settings");
let value = this.read("mx_local_settings");
if (!value) value = {};
if (!value["blacklistUnverifiedDevicesPerRoom"]) value["blacklistUnverifiedDevicesPerRoom"] = {};
value["blacklistUnverifiedDevicesPerRoom"][roomId] = newValue;
localStorage.setItem("mx_local_settings", JSON.stringify(value));
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
return Promise.resolve();
}
if (newValue === null) {
localStorage.removeItem(this._getKey(settingName, roomId));
localStorage.removeItem(this.getKey(settingName, roomId));
} else {
newValue = JSON.stringify({value: newValue});
localStorage.setItem(this._getKey(settingName, roomId), newValue);
localStorage.setItem(this.getKey(settingName, roomId), newValue);
}
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM_DEVICE, newValue);
return Promise.resolve();
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
return true; // It's their device, so they should be able to
}
isSupported() {
public isSupported(): boolean {
return localStorage !== undefined && localStorage !== null;
}
_read(key) {
private read(key: string): any {
const rawValue = localStorage.getItem(key);
if (!rawValue) return null;
return JSON.parse(rawValue);
}
_getKey(settingName, roomId) {
private getKey(settingName: string, roomId: string): string {
return "mx_setting_" + settingName + "_" + roomId;
}
}

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -15,31 +15,32 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {MatrixClientPeg} from '../../MatrixClientPeg';
import { MatrixClientPeg } from '../../MatrixClientPeg';
import MatrixClientBackedSettingsHandler from "./MatrixClientBackedSettingsHandler";
import {SettingLevel} from "../SettingsStore";
import {objectClone, objectKeyChanges} from "../../utils/objects";
import { objectClone, objectKeyChanges } from "../../utils/objects";
import { SettingLevel } from "../SettingLevel";
import { WatchManager } from "../WatchManager";
import { MatrixClient } from "matrix-js-sdk/src/client";
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { RoomState } from "matrix-js-sdk/src/models/room-state";
/**
* Gets and sets settings at the "room" level.
*/
export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandler {
constructor(watchManager) {
constructor(private watchers: WatchManager) {
super();
this._watchers = watchManager;
this._onEvent = this._onEvent.bind(this);
}
initMatrixClient(oldClient, newClient) {
protected initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient) {
if (oldClient) {
oldClient.removeListener("RoomState.events", this._onEvent);
oldClient.removeListener("RoomState.events", this.onEvent);
}
newClient.on("RoomState.events", this._onEvent);
newClient.on("RoomState.events", this.onEvent);
}
_onEvent(event, state, prevEvent) {
private onEvent = (event: MatrixEvent, state: RoomState, prevEvent: MatrixEvent) => {
const roomId = event.getRoomId();
const room = this.client.getRoom(roomId);
@ -60,45 +61,45 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
val = !val;
}
this._watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
this.watchers.notifyUpdate("urlPreviewsEnabled", roomId, SettingLevel.ROOM, val);
} else if (event.getType() === "im.vector.web.settings") {
// Figure out what changed and fire those updates
const prevContent = prevEvent ? prevEvent.getContent() : {};
const changedSettings = objectKeyChanges(prevContent, event.getContent());
for (const settingName of changedSettings) {
this._watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
this.watchers.notifyUpdate(settingName, roomId, SettingLevel.ROOM, event.getContent()[settingName]);
}
}
}
};
getValue(settingName, roomId) {
public getValue(settingName: string, roomId: string): any {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
// Check to make sure that we actually got a boolean
if (typeof(content['disable']) !== "boolean") return null;
if (typeof (content['disable']) !== "boolean") return null;
return !content['disable'];
}
const settings = this._getSettings(roomId) || {};
const settings = this.getSettings(roomId) || {};
return settings[settingName];
}
setValue(settingName, roomId, newValue) {
public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
// Special case URL previews
if (settingName === "urlPreviewsEnabled") {
const content = this._getSettings(roomId, "org.matrix.room.preview_urls") || {};
const content = this.getSettings(roomId, "org.matrix.room.preview_urls") || {};
content['disable'] = !newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "org.matrix.room.preview_urls", content);
}
const content = this._getSettings(roomId) || {};
const content = this.getSettings(roomId) || {};
content[settingName] = newValue;
return MatrixClientPeg.get().sendStateEvent(roomId, "im.vector.web.settings", content, "");
}
canSetValue(settingName, roomId) {
public canSetValue(settingName: string, roomId: string): boolean {
const cli = MatrixClientPeg.get();
const room = cli.getRoom(roomId);
@ -109,12 +110,12 @@ export default class RoomSettingsHandler extends MatrixClientBackedSettingsHandl
return room.currentState.maySendStateEvent(eventType, cli.getUserId());
}
isSupported() {
public isSupported(): boolean {
const cli = MatrixClientPeg.get();
return cli !== undefined && cli !== null;
}
_getSettings(roomId, eventType = "im.vector.web.settings") {
private getSettings(roomId: string, eventType = "im.vector.web.settings"): any {
const room = MatrixClientPeg.get().getRoom(roomId);
if (!room) return null;

View file

@ -1,6 +1,6 @@
/*
Copyright 2017 Travis Ralston
Copyright 2019 New Vector Ltd.
Copyright 2019, 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.
@ -19,7 +19,7 @@ limitations under the License.
* Represents the base class for all level handlers. This class performs no logic
* and should be overridden.
*/
export default class SettingsHandler {
export default abstract class SettingsHandler {
/**
* Gets the value for a particular setting at this level for a particular room.
* If no room is applicable, the roomId may be null. The roomId may not be
@ -28,10 +28,7 @@ export default class SettingsHandler {
* @param {String} roomId The room ID to read from, may be null.
* @returns {*} The setting value, or null if not found.
*/
getValue(settingName, roomId) {
console.error("Invalid operation: getValue was not overridden");
return null;
}
public abstract getValue(settingName: string, roomId: string): any;
/**
* Sets the value for a particular setting at this level for a particular room.
@ -44,10 +41,7 @@ export default class SettingsHandler {
* @param {*} newValue The new value for the setting, may be null.
* @returns {Promise} Resolves when the setting has been saved.
*/
setValue(settingName, roomId, newValue) {
console.error("Invalid operation: setValue was not overridden");
return Promise.reject();
}
public abstract setValue(settingName: string, roomId: string, newValue: any): Promise<void>;
/**
* Determines if the current user is able to set the value of the given setting
@ -56,15 +50,11 @@ export default class SettingsHandler {
* @param {String} roomId The room ID to check in, may be null
* @returns {boolean} True if the setting can be set by the user, false otherwise.
*/
canSetValue(settingName, roomId) {
return false;
}
public abstract canSetValue(settingName: string, roomId: string): boolean;
/**
* Determines if this level is supported on this device.
* @returns {boolean} True if this level is supported on the current device.
*/
isSupported() {
return false;
}
public abstract isSupported(): boolean;
}

View file

@ -15,10 +15,11 @@ limitations under the License.
*/
import dis from '../../dispatcher/dispatcher';
import SettingsStore, {SettingLevel} from '../SettingsStore';
import SettingsStore from '../SettingsStore';
import IWatcher from "./Watcher";
import { toPx } from '../../utils/units';
import { Action } from '../../dispatcher/actions';
import { SettingLevel } from "../SettingLevel";
export class FontWatcher implements IWatcher {
public static readonly MIN_SIZE = 8;

View file

@ -15,17 +15,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsStore, { SettingLevel } from '../SettingsStore';
import SettingsStore from '../SettingsStore';
import dis from '../../dispatcher/dispatcher';
import { Action } from '../../dispatcher/actions';
import ThemeController from "../controllers/ThemeController";
import { setTheme } from "../../theme";
import { ActionPayload } from '../../dispatcher/payloads';
import { SettingLevel } from "../SettingLevel";
export default class ThemeWatcher {
// XXX: I think this is unused.
static _instance = null;
private themeWatchRef: string;
private systemThemeWatchRef: string;
private dispatcherRef: string;

View file

@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import SettingsStore, { SettingLevel } from "../settings/SettingsStore";
import SettingsStore from "../settings/SettingsStore";
import { Room } from "matrix-js-sdk/src/models/room";
import { ActionPayload } from "../dispatcher/payloads";
import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import defaultDispatcher from "../dispatcher/dispatcher";
import { arrayHasDiff } from "../utils/arrays";
import { isNullOrUndefined } from "matrix-js-sdk/src/utils";
import { SettingLevel } from "../settings/SettingLevel";
const MAX_ROOMS = 20; // arbitrary
const AUTOJOIN_WAIT_THRESHOLD_MS = 90000; // 90s, the time we wait for an autojoined room to show up

View file

@ -17,10 +17,11 @@ limitations under the License.
import dis from '../dispatcher/dispatcher';
import {pendingVerificationRequestForUser} from '../verification';
import {Store} from 'flux/utils';
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import SettingsStore from "../settings/SettingsStore";
import {RightPanelPhases, RIGHT_PANEL_PHASES_NO_ARGS} from "./RightPanelStorePhases";
import {ActionPayload} from "../dispatcher/payloads";
import {Action} from '../dispatcher/actions';
import { SettingLevel } from "../settings/SettingLevel";
interface RightPanelStoreState {
// Whether or not to show the right panel at all. We split out rooms and groups