diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000000..d41aebad3c --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,151 @@ +# Settings Reference + +This document serves as developer documentation for using "Granular Settings". Granular Settings allow users to specify different values for a setting at particular levels of interest. For example, a user may say that in a particular room they want URL previews off, but in all other rooms they want them enabled. The `SettingsStore` helps mask the complexity of dealing with the different levels and exposes easy to use getters and setters. + + +## Levels + +Granular Settings rely on a series of known levels in order to use the correct value for the scenario. These levels, in order of prioirty, are: +* `device` - The current user's device +* `room-device` - The current user's device, but only when in a specific room +* `room-account` - The current user's account, but only when in a specific room +* `account` - The current user's account +* `room` - A specific room (setting for all members of the room) +* `config` - Values are defined by `config.json` +* `default` - The hardcoded default for the settings + +Individual settings may control which levels are appropriate for them as part of the defaults. This is often to ensure that room administrators cannot force account-only settings upon participants. + + +## Settings + +Settings are the different options a user may set or experience in the application. These are pre-defined in `src/settings/Settings.js` under the `SETTINGS` constant and have the following minimum requirements: +``` +// The ID is used to reference the setting throughout the application. This must be unique. +"theSettingId": { + // The levels this setting supports is required. In `src/settings/Settings.js` there are various pre-set arrays + // for this option - they should be used where possible to avoid copy/pasting arrays across settings. + supportedLevels: [...], + + // The default for this setting serves two purposes: It provides a value if the setting is not defined at other + // levels, and it serves to demonstrate the expected type to other developers. The value isn't enforced, but it + // should be respected throughout the code. The default may be any data type. + default: false, + + // The display name has two notations: string and object. The object notation allows for different translatable + // strings to be used for different levels, while the string notation represents the string for all levels. + + displayName: _td("Change something"), // effectively `displayName: { "default": _td("Change something") }` + displayName: { + "room": _td("Change something for participants of this room"), + + // Note: the default will be used if the level requested (such as `device`) does not have a string defined here. + "default": _td("Change something"), + } +} +``` + +### Getting values for a setting + +After importing `SettingsStore`, simply make a call to `SettingsStore.getValue`. The `roomId` parameter should always be supplied where possible, even if the setting does not have a per-room level value. This is to ensure that the value returned is best represented in the room, particularly if the setting ever gets a per-room level in the future. + +In settings pages it is often desired to have the value at a particular level instead of getting the calculated value. Call `SettingsStore.getValueAt` to get the value of a setting at a particular level, and optionally make it explicitly at that level. By default `getValueAt` will traverse the tree starting at the provided level; making it explicit means it will not go beyond the provided level. When using `getValueAt`, please be sure to use `SettingLevel` to represent the target level. + +### Setting values for a setting + +Values are defined at particular levels and should be done in a safe manner. There are two checks to perform to ensure a clean save: is the level supported and can the user actually set the value. In most cases, neither should be an issue although there are circumstances where this changes. An example of a safe call is: +```javascript +const isSupported = SettingsStore.isLevelSupported(SettingLevel.ROOM); +if (isSupported) { + const canSetValue = SettingsStore.canSetValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM); + if (canSetValue) { + SettingsStore.setValue("mySetting", "!curbf:matrix.org", SettingLevel.ROOM, newValue); + } +} +``` + +These checks may also be performed in different areas of the application to avoid the verbose example above. For instance, the component which allows changing the setting may be hidden conditionally on the above conditions. + +##### `SettingsFlag` component + +Where possible, the `SettingsFlag` component should be used to set simple "flip-a-bit" (true/false) settings. The `SettingsFlag` also supports simple radio button options, such as the theme the user would like to use. +```html + +``` + +### Getting the display name for a setting + +Simply call `SettingsStore.getDisplayName`. The appropriate display name will be returned and automatically translated for you. If a display name cannot be found, it will return `null`. + + +## Features + +Occasionally some parts of the application may be undergoing testing and are not quite production ready. These are commonly known to be behind a "labs flag". Features behind lab flags must go through the granular settings system, and look and act very much normal settings. The exception is that they must supply `isFeature: true` as part of the setting definition and should go through the helper functions on `SettingsStore`. + +### Determining if a feature is enabled + +A simple call to `SettingsStore.isFeatureEnabled` will tell you if the feature is enabled. This will perform all the required calculations to determine if the feature is enabled based upon the configuration and user selection. + +### Enabling a feature + +Features can only be enabled if the feature is in the `labs` state, otherwise this is a no-op. To find the current set of features in the `labs` state, call `SettingsStore.getLabsFeatures`. To set the value, call `SettingsStore.setFeatureEnabled`. + + +## Setting controllers + +Settings may have environmental factors that affect their value or need additional code to be called when they are modified. A setting controller is able to override the calculated value for a setting and react to changes in that setting. Controllers are not a replacement for the level handlers and should only be used to ensure the environment is kept up to date with the setting where it is otherwise not possible. An example of this is the notification settings: they can only be considered enabled if the platform supports notifications, and enabling notifications requires additional steps to actually enable notifications. + +For more information, see `src/settings/controllers/SettingController.js`. + + +## Local echo + +`SettingsStore` will perform local echo on all settings to ensure that immediately getting values does not cause a split-brain scenario. As mentioned in the "Setting values for a setting" section, the appropriate checks should be done to ensure that the user is allowed to set the value. The local echo system assumes that the user has permission and that the request will go through successfully. The local echo only takes effect until the request to save a setting has completed (either successfully or otherwise). + +```javascript +SettingsStore.setValue(...).then(() => { + // The value has actually been stored at this point. +}); +SettingsStore.getValue(...); // this will return the value set in `setValue` above. +``` + + + +# Maintainers Reference + +The granular settings system has a few complex parts to power it. This section is to document how the `SettingsStore` is supposed to work. + +### General information + +The `SettingsStore` uses the hardcoded `LEVEL_ORDER` constant to ensure that it is using the correct override procedure. The array is checked from left to right, simulating the behaviour of overriding values from the higher levels. Each level should be defined in this array, including `default`. + +Handlers (`src/settings/handlers/SettingsHandler.js`) represent a single level and are responsible for getting and setting values at that level. Handlers also provide additional information to the `SettingsStore` such as if the level is supported or if the current user may set values at the level. The `SettingsStore` will use the handler to enforce checks and manipulate settings. Handlers are also responsible for dealing with migration patterns or legacy settings for their level (for example, a setting being renamed or using a different key from other settings in the underlying store). Handlers are provided to the `SettingsStore` via the `LEVEL_HANDLERS` constant. `SettingsStore` will optimize lookups by only considering handlers that are supported on the platform. + +Local echo is achieved through `src/settings/handlers/LocalEchoWrapper.js` which acts as a wrapper around a given handler. This is automatically applied to all defined `LEVEL_HANDLERS` and proxies the calls to the wrapped handler where possible. The echo is achieved by a simple object cache stored within the class itself. The cache is invalidated immediately upon the proxied save call succeeding or failing. + +Controllers are notified of changes by the `SettingsStore`, and are given the opportunity to override values after the `SettingsStore` has deemed the value calculated. Controllers are invoked as the last possible step in the code. + +### Features + +Features automatically get considered as `disabled` if they are not listed in the `SdkConfig` or `enable_labs` is false/not set. Features are always checked against the configuration before going through the level order as they have the option of being forced-on or forced-off for the application. This is done by the `features` section and looks something like this: + +``` +"features": { + "feature_groups": "enable", + "feature_pinning": "disable", // the default + "feature_presence": "labs" +} +``` + +If `enableLabs` is true in the configuration, the default for features becomes `"labs"`. diff --git a/src/CallHandler.js b/src/CallHandler.js index a9539d40e1..7dbd0c899b 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -52,13 +52,13 @@ limitations under the License. */ import MatrixClientPeg from './MatrixClientPeg'; -import UserSettingsStore from './UserSettingsStore'; import PlatformPeg from './PlatformPeg'; import Modal from './Modal'; import sdk from './index'; import { _t } from './languageHandler'; import Matrix from 'matrix-js-sdk'; import dis from './dispatcher'; +import SettingsStore from "./settings/SettingsStore"; global.mxCalls = { //room_id: MatrixCall @@ -246,7 +246,7 @@ function _onAction(payload) { } else if (members.length === 2) { console.log("Place %s call in %s", payload.type, payload.room_id); const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, { - forceTURN: UserSettingsStore.getLocalSetting('webRtcForceTURN', false), + forceTURN: SettingsStore.getValue('webRtcForceTURN'), }); placeCall(call); } else { // > 2 diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js index 839b496845..cdc5c61921 100644 --- a/src/CallMediaHandler.js +++ b/src/CallMediaHandler.js @@ -14,8 +14,8 @@ limitations under the License. */ -import UserSettingsStore from './UserSettingsStore'; import * as Matrix from 'matrix-js-sdk'; +import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; export default { getDevices: function() { @@ -43,22 +43,20 @@ export default { }, loadDevices: function() { - // this.getDevices().then((devices) => { - const localSettings = UserSettingsStore.getLocalSettings(); - // // if deviceId is not found, automatic fallback is in spec - // // recall previously stored inputs if any - Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']); - Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']); - // }); + const audioDeviceId = SettingsStore.getValue("webrtc_audioinput"); + const videoDeviceId = SettingsStore.getValue("webrtc_videoinput"); + + Matrix.setMatrixCallAudioInput(audioDeviceId); + Matrix.setMatrixCallVideoInput(videoDeviceId); }, setAudioInput: function(deviceId) { - UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId); + SettingsStore.setValue("webrtc_audioinput", null, SettingLevel.DEVICE, deviceId); Matrix.setMatrixCallAudioInput(deviceId); }, setVideoInput: function(deviceId) { - UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId); + SettingsStore.setValue("webrtc_videoinput", null, SettingLevel.DEVICE, deviceId); Matrix.setMatrixCallVideoInput(deviceId); }, }; diff --git a/src/Lifecycle.js b/src/Lifecycle.js index 4d8911f7a6..946f22537d 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -436,6 +436,10 @@ function startMatrixClient() { DMRoomMap.makeShared().start(); MatrixClientPeg.start(); + + // dispatch that we finished starting up to wire up any other bits + // of the matrix client that cannot be set prior to starting up. + dis.dispatch({action: 'client_started'}); } /* diff --git a/src/Notifier.js b/src/Notifier.js index 93ef192fe0..75b698862c 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -25,6 +25,7 @@ import dis from './dispatcher'; import sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; +import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; /* * Dispatches: @@ -138,10 +139,8 @@ const Notifier = { // make sure that we persist the current setting audio_enabled setting // before changing anything - if (global.localStorage) { - if (global.localStorage.getItem('audio_notifications_enabled') === null) { - this.setAudioEnabled(this.isEnabled()); - } + if (SettingsStore.isLevelSupported(SettingLevel.DEVICE)) { + SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, this.isEnabled()); } if (enable) { @@ -149,6 +148,7 @@ const Notifier = { plaf.requestNotificationPermission().done((result) => { if (result !== 'granted') { // The permission request was dismissed or denied + // TODO: Support alternative branding in messaging const description = result === 'denied' ? _t('Riot does not have permission to send you notifications - please check your browser settings') : _t('Riot was not given permission to send notifications - please try again'); @@ -160,10 +160,6 @@ const Notifier = { return; } - if (global.localStorage) { - global.localStorage.setItem('notifications_enabled', 'true'); - } - if (callback) callback(); dis.dispatch({ action: "notifier_enabled", @@ -174,8 +170,6 @@ const Notifier = { // disabled again in the future, we will show the banner again. this.setToolbarHidden(false); } else { - if (!global.localStorage) return; - global.localStorage.setItem('notifications_enabled', 'false'); dis.dispatch({ action: "notifier_enabled", value: false, @@ -184,44 +178,24 @@ const Notifier = { }, isEnabled: function() { + return this.isPossible() && SettingsStore.getValue("notificationsEnabled"); + }, + + isPossible: function() { const plaf = PlatformPeg.get(); if (!plaf) return false; if (!plaf.supportsNotifications()) return false; if (!plaf.maySendNotifications()) return false; - if (!global.localStorage) return true; - - const enabled = global.localStorage.getItem('notifications_enabled'); - if (enabled === null) return true; - return enabled === 'true'; - }, - - setBodyEnabled: function(enable) { - if (!global.localStorage) return; - global.localStorage.setItem('notifications_body_enabled', enable ? 'true' : 'false'); + return true; // possible, but not necessarily enabled }, isBodyEnabled: function() { - if (!global.localStorage) return true; - const enabled = global.localStorage.getItem('notifications_body_enabled'); - // default to true if the popups are enabled - if (enabled === null) return this.isEnabled(); - return enabled === 'true'; + return this.isEnabled() && SettingsStore.getValue("notificationBodyEnabled"); }, - setAudioEnabled: function(enable) { - if (!global.localStorage) return; - global.localStorage.setItem('audio_notifications_enabled', - enable ? 'true' : 'false'); - }, - - isAudioEnabled: function(enable) { - if (!global.localStorage) return true; - const enabled = global.localStorage.getItem( - 'audio_notifications_enabled'); - // default to true if the popups are enabled - if (enabled === null) return this.isEnabled(); - return enabled === 'true'; + isAudioEnabled: function() { + return this.isEnabled() && SettingsStore.getValue("audioNotificationsEnabled"); }, setToolbarHidden: function(hidden, persistent = true) { @@ -238,16 +212,14 @@ const Notifier = { // update the info to localStorage for persistent settings if (persistent && global.localStorage) { - global.localStorage.setItem('notifications_hidden', hidden); + global.localStorage.setItem("notifications_hidden", hidden); } }, isToolbarHidden: function() { // Check localStorage for any such meta data if (global.localStorage) { - if (global.localStorage.getItem('notifications_hidden') === 'true') { - return true; - } + return global.localStorage.getItem("notifications_hidden") === "true"; } return this.toolbarHidden; diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 82665cc2f3..344bac1ddb 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -20,6 +20,7 @@ import Tinter from "./Tinter"; import sdk from './index'; import { _t } from './languageHandler'; import Modal from './Modal'; +import SettingsStore, {SettingLevel} from "./settings/SettingsStore"; class Command { @@ -97,9 +98,7 @@ const commands = { colorScheme.secondary_color = matches[4]; } return success( - MatrixClientPeg.get().setRoomAccountData( - roomId, "org.matrix.room.color_scheme", colorScheme, - ), + SettingsStore.setValue("roomColor", roomId, SettingLevel.ROOM_ACCOUNT, colorScheme), ); } } diff --git a/src/Unread.js b/src/Unread.js index 20e876ad88..383b5c2e5a 100644 --- a/src/Unread.js +++ b/src/Unread.js @@ -15,7 +15,6 @@ limitations under the License. */ const MatrixClientPeg = require('./MatrixClientPeg'); -import UserSettingsStore from './UserSettingsStore'; import shouldHideEvent from './shouldHideEvent'; const sdk = require('./index'); @@ -64,7 +63,6 @@ module.exports = { // we have and the read receipt. We could fetch more history to try & find out, // but currently we just guess. - const syncedSettings = UserSettingsStore.getSyncedSettings(); // Loop through messages, starting with the most recent... for (let i = room.timeline.length - 1; i >= 0; --i) { const ev = room.timeline[i]; @@ -74,7 +72,7 @@ module.exports = { // that counts and we can stop looking because the user's read // this and everything before. return false; - } else if (!shouldHideEvent(ev, syncedSettings) && this.eventTriggersUnreadCount(ev)) { + } else if (!shouldHideEvent(ev) && this.eventTriggersUnreadCount(ev)) { // We've found a message that counts before we hit // the read marker, so this room is definitely unread. return true; diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 163ef75c50..5d2af3715f 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -17,58 +17,11 @@ limitations under the License. import Promise from 'bluebird'; import MatrixClientPeg from './MatrixClientPeg'; -import Notifier from './Notifier'; -import { _t, _td } from './languageHandler'; -import SdkConfig from './SdkConfig'; /* * TODO: Make things use this. This is all WIP - see UserSettings.js for usage. */ - -const FEATURES = [ - { - id: 'feature_pinning', - name: _td("Message Pinning"), - }, - { - id: 'feature_presence_management', - name: _td("Presence Management"), - }, -]; - export default { - getLabsFeatures() { - const featuresConfig = SdkConfig.get()['features'] || {}; - - // The old flag: honoured for backwards compatibility - const enableLabs = SdkConfig.get()['enableLabs']; - - let labsFeatures; - if (enableLabs) { - labsFeatures = FEATURES; - } else { - labsFeatures = FEATURES.filter((f) => { - const sdkConfigValue = featuresConfig[f.id]; - if (sdkConfigValue === 'labs') { - return true; - } - }); - } - return labsFeatures.map((f) => { - return f.id; - }); - }, - - translatedNameForFeature(featureId) { - const feature = FEATURES.filter((f) => { - return f.id === featureId; - })[0]; - - if (feature === undefined) return null; - - return _t(feature.name); - }, - loadProfileInfo: function() { const cli = MatrixClientPeg.get(); return cli.getProfileInfo(cli.credentials.userId); @@ -91,36 +44,6 @@ export default { // TODO }, - getEnableNotifications: function() { - return Notifier.isEnabled(); - }, - - setEnableNotifications: function(enable) { - if (!Notifier.supportsDesktopNotifications()) { - return; - } - Notifier.setEnabled(enable); - }, - - getEnableNotificationBody: function() { - return Notifier.isBodyEnabled(); - }, - - setEnableNotificationBody: function(enable) { - if (!Notifier.supportsDesktopNotifications()) { - return; - } - Notifier.setBodyEnabled(enable); - }, - - getEnableAudioNotifications: function() { - return Notifier.isAudioEnabled(); - }, - - setEnableAudioNotifications: function(enable) { - Notifier.setAudioEnabled(enable); - }, - changePassword: function(oldPassword, newPassword) { const cli = MatrixClientPeg.get(); @@ -167,97 +90,4 @@ export default { append: true, // We always append for email pushers since we don't want to stop other accounts notifying to the same email address }); }, - - getUrlPreviewsDisabled: function() { - const event = MatrixClientPeg.get().getAccountData('org.matrix.preview_urls'); - return (event && event.getContent().disable); - }, - - setUrlPreviewsDisabled: function(disabled) { - // FIXME: handle errors - return MatrixClientPeg.get().setAccountData('org.matrix.preview_urls', { - disable: disabled, - }); - }, - - getTheme: function() { - let syncedSettings; - let theme; - if (MatrixClientPeg.get()) { - syncedSettings = this.getSyncedSettings(); - } - if (!syncedSettings || !syncedSettings.theme) { - theme = (SdkConfig.get() ? SdkConfig.get().default_theme : undefined) || 'light'; - } else { - theme = syncedSettings.theme; - } - return theme; - }, - - getSyncedSettings: function() { - const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings'); - return event ? event.getContent() : {}; - }, - - getSyncedSetting: function(type, defaultValue = null) { - const settings = this.getSyncedSettings(); - return settings.hasOwnProperty(type) ? settings[type] : defaultValue; - }, - - setSyncedSetting: function(type, value) { - const settings = this.getSyncedSettings(); - settings[type] = value; - // FIXME: handle errors - return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings); - }, - - getLocalSettings: function() { - const localSettingsString = localStorage.getItem('mx_local_settings') || '{}'; - return JSON.parse(localSettingsString); - }, - - getLocalSetting: function(type, defaultValue = null) { - const settings = this.getLocalSettings(); - return settings.hasOwnProperty(type) ? settings[type] : defaultValue; - }, - - setLocalSetting: function(type, value) { - const settings = this.getLocalSettings(); - settings[type] = value; - // FIXME: handle errors - localStorage.setItem('mx_local_settings', JSON.stringify(settings)); - }, - - isFeatureEnabled: function(featureId: string): boolean { - const featuresConfig = SdkConfig.get()['features']; - - // The old flag: honoured for backwards compatibility - const enableLabs = SdkConfig.get()['enableLabs']; - - let sdkConfigValue = enableLabs ? 'labs' : 'disable'; - if (featuresConfig && featuresConfig[featureId] !== undefined) { - sdkConfigValue = featuresConfig[featureId]; - } - - if (sdkConfigValue === 'enable') { - return true; - } else if (sdkConfigValue === 'disable') { - return false; - } else if (sdkConfigValue === 'labs') { - if (!MatrixClientPeg.get().isGuest()) { - // Make it explicit that guests get the defaults (although they shouldn't - // have been able to ever toggle the flags anyway) - const userValue = localStorage.getItem(`mx_labs_feature_${featureId}`); - return userValue === 'true'; - } - return false; - } else { - console.warn(`Unknown features config for ${featureId}: ${sdkConfigValue}`); - return false; - } - }, - - setFeatureEnabled: function(featureId: string, enabled: boolean) { - localStorage.setItem(`mx_labs_feature_${featureId}`, enabled); - }, }; diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index 9f1f40dbe7..f4e576ea0f 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -26,7 +26,7 @@ import {PillCompletion} from './Components'; import type {SelectionRange, Completion} from './Autocompleter'; import _uniq from 'lodash/uniq'; import _sortBy from 'lodash/sortBy'; -import UserSettingsStore from '../UserSettingsStore'; +import SettingsStore from "../settings/SettingsStore"; import EmojiData from '../stripped-emoji.json'; @@ -96,7 +96,7 @@ export default class EmojiProvider extends AutocompleteProvider { } async getCompletions(query: string, selection: SelectionRange) { - if (UserSettingsStore.getSyncedSetting("MessageComposerInput.dontSuggestEmoji")) { + if (SettingsStore.getValue("MessageComposerInput.dontSuggestEmoji")) { return []; // don't give any suggestions if the user doesn't want them } diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index 08120d9508..9a293bfc8a 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -19,7 +19,6 @@ limitations under the License. import * as Matrix from 'matrix-js-sdk'; import React from 'react'; -import UserSettingsStore from '../../UserSettingsStore'; import KeyCode from '../../KeyCode'; import Notifier from '../../Notifier'; import PageTypes from '../../PageTypes'; @@ -28,6 +27,7 @@ import sdk from '../../index'; import dis from '../../dispatcher'; import sessionStore from '../../stores/SessionStore'; import MatrixClientPeg from '../../MatrixClientPeg'; +import SettingsStore from "../../settings/SettingsStore"; /** * This is what our MatrixChat shows when we are logged in. The precise view is @@ -74,7 +74,7 @@ export default React.createClass({ getInitialState: function() { return { // use compact timeline view - useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'), + useCompactLayout: SettingsStore.getValue('useCompactLayout'), }; }, diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 183db8a756..37005b0d69 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -22,7 +22,6 @@ import React from 'react'; import Matrix from "matrix-js-sdk"; import Analytics from "../../Analytics"; -import UserSettingsStore from '../../UserSettingsStore'; import MatrixClientPeg from "../../MatrixClientPeg"; import PlatformPeg from "../../PlatformPeg"; import SdkConfig from "../../SdkConfig"; @@ -44,6 +43,7 @@ import createRoom from "../../createRoom"; import * as UDEHandler from '../../UnknownDeviceErrorHandler'; import KeyRequestHandler from '../../KeyRequestHandler'; import { _t, getCurrentLanguage } from '../../languageHandler'; +import SettingsStore, {SettingLevel} from "../../settings/SettingsStore"; /** constants for MatrixChat.state.view */ const VIEWS = { @@ -224,7 +224,7 @@ module.exports = React.createClass({ componentWillMount: function() { SdkConfig.put(this.props.config); - if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable(); + if (!SettingsStore.getValue("analyticsOptOut")) Analytics.enable(); // Used by _viewRoom before getting state from sync this.firstSyncComplete = false; @@ -591,6 +591,9 @@ module.exports = React.createClass({ this._onWillStartClient(); }); break; + case 'client_started': + this._onClientStarted(); + break; case 'new_version': this.onVersion( payload.currentVersion, payload.newVersion, @@ -1124,6 +1127,34 @@ module.exports = React.createClass({ cli.on("crypto.roomKeyRequestCancellation", (req) => { krh.handleKeyRequestCancellation(req); }); + cli.on("Room", (room) => { + if (MatrixClientPeg.get().isCryptoEnabled()) { + const blacklistEnabled = SettingsStore.getValueAt( + SettingLevel.ROOM_DEVICE, + "blacklistUnverifiedDevices", + room.roomId, + /*explicit=*/true, + ); + room.setBlacklistUnverifiedDevices(blacklistEnabled); + } + }); + }, + + /** + * Called shortly after the matrix client has started. Useful for + * setting up anything that requires the client to be started. + * @private + */ + _onClientStarted: function() { + const cli = MatrixClientPeg.get(); + + if (cli.isCryptoEnabled()) { + const blacklistEnabled = SettingsStore.getValueAt( + SettingLevel.DEVICE, + "blacklistUnverifiedDevices", + ); + cli.setGlobalBlacklistUnverifiedDevices(blacklistEnabled); + } }, showScreen: function(screen, params) { diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 2331e096c0..53cc660a9b 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -17,7 +17,6 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; -import UserSettingsStore from '../../UserSettingsStore'; import shouldHideEvent from '../../shouldHideEvent'; import dis from "../../dispatcher"; import sdk from '../../index'; @@ -110,8 +109,6 @@ module.exports = React.createClass({ // Velocity requires this._readMarkerGhostNode = null; - this._syncedSettings = UserSettingsStore.getSyncedSettings(); - this._isMounted = true; }, @@ -251,7 +248,7 @@ module.exports = React.createClass({ // Always show highlighted event if (this.props.highlightedEventId === mxEv.getId()) return true; - return !shouldHideEvent(mxEv, this._syncedSettings); + return !shouldHideEvent(mxEv); }, _getEventTiles: function() { diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index 409b95947f..38a3392e43 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -29,7 +29,6 @@ const classNames = require("classnames"); const Matrix = require("matrix-js-sdk"); import { _t } from '../../languageHandler'; -const UserSettingsStore = require('../../UserSettingsStore'); const MatrixClientPeg = require("../../MatrixClientPeg"); const ContentMessages = require("../../ContentMessages"); const Modal = require("../../Modal"); @@ -46,6 +45,7 @@ import KeyCode from '../../KeyCode'; import RoomViewStore from '../../stores/RoomViewStore'; import RoomScrollStateStore from '../../stores/RoomScrollStateStore'; +import SettingsStore from "../../settings/SettingsStore"; const DEBUG = false; let debuglog = function() {}; @@ -149,8 +149,6 @@ module.exports = React.createClass({ MatrixClientPeg.get().on("RoomMember.membership", this.onRoomMemberMembership); MatrixClientPeg.get().on("accountData", this.onAccountData); - this._syncedSettings = UserSettingsStore.getSyncedSettings(); - // Start listening for RoomViewStore updates this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate); this._onRoomViewStoreUpdate(true); @@ -542,7 +540,7 @@ module.exports = React.createClass({ // update unread count when scrolled up if (!this.state.searchResults && this.state.atEndOfLiveTimeline) { // no change - } else if (!shouldHideEvent(ev, this._syncedSettings)) { + } else if (!shouldHideEvent(ev)) { this.setState((state, props) => { return {numUnreadMessages: state.numUnreadMessages + 1}; }); @@ -616,38 +614,8 @@ module.exports = React.createClass({ }, _updatePreviewUrlVisibility: function(room) { - // console.log("_updatePreviewUrlVisibility"); - - // check our per-room overrides - const roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls"); - if (roomPreviewUrls && roomPreviewUrls.getContent().disable !== undefined) { - this.setState({ - showUrlPreview: !roomPreviewUrls.getContent().disable, - }); - return; - } - - // check our global disable override - const userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls"); - if (userRoomPreviewUrls && userRoomPreviewUrls.getContent().disable) { - this.setState({ - showUrlPreview: false, - }); - return; - } - - // check the room state event - const roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', ''); - if (roomStatePreviewUrls && roomStatePreviewUrls.getContent().disable) { - this.setState({ - showUrlPreview: false, - }); - return; - } - - // otherwise, we assume they're on. this.setState({ - showUrlPreview: true, + showUrlPreview: SettingsStore.getValue("urlPreviewsEnabled", room.roomId), }); }, @@ -666,12 +634,7 @@ module.exports = React.createClass({ const room = this.state.room; if (!room) return; - const color_scheme_event = room.getAccountData("org.matrix.room.color_scheme"); - let color_scheme = {}; - if (color_scheme_event) { - color_scheme = color_scheme_event.getContent(); - // XXX: we should validate the event - } + const color_scheme = SettingsStore.getValue("roomColor", room.room_id); console.log("Tinter.tint from updateTint"); Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color); }, @@ -1775,7 +1738,7 @@ module.exports = React.createClass({ const messagePanel = (