Convert local settings to granular settings

This breaks language selection.

Signed-off-by: Travis Ralston <travpc@gmail.com>
pull/21833/head
Travis Ralston 2017-10-29 16:53:00 -06:00
parent b3d17a7b51
commit 0d3f0eaf98
11 changed files with 83 additions and 77 deletions

View File

@ -52,13 +52,13 @@ limitations under the License.
*/ */
import MatrixClientPeg from './MatrixClientPeg'; import MatrixClientPeg from './MatrixClientPeg';
import UserSettingsStore from './UserSettingsStore';
import PlatformPeg from './PlatformPeg'; import PlatformPeg from './PlatformPeg';
import Modal from './Modal'; import Modal from './Modal';
import sdk from './index'; import sdk from './index';
import { _t } from './languageHandler'; import { _t } from './languageHandler';
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import dis from './dispatcher'; import dis from './dispatcher';
import SettingsStore from "./settings/SettingsStore";
global.mxCalls = { global.mxCalls = {
//room_id: MatrixCall //room_id: MatrixCall
@ -246,7 +246,7 @@ function _onAction(payload) {
} else if (members.length === 2) { } else if (members.length === 2) {
console.log("Place %s call in %s", payload.type, payload.room_id); console.log("Place %s call in %s", payload.type, payload.room_id);
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, { const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, {
forceTURN: UserSettingsStore.getLocalSetting('webRtcForceTURN', false), forceTURN: SettingsStore.getValue('webRtcForceTURN'),
}); });
placeCall(call); placeCall(call);
} else { // > 2 } else { // > 2

View File

@ -14,8 +14,8 @@
limitations under the License. limitations under the License.
*/ */
import UserSettingsStore from './UserSettingsStore';
import * as Matrix from 'matrix-js-sdk'; import * as Matrix from 'matrix-js-sdk';
import SettingsStore from "./settings/SettingsStore";
export default { export default {
getDevices: function() { getDevices: function() {
@ -43,22 +43,20 @@ export default {
}, },
loadDevices: function() { loadDevices: function() {
// this.getDevices().then((devices) => { const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
const localSettings = UserSettingsStore.getLocalSettings(); const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
// // if deviceId is not found, automatic fallback is in spec
// // recall previously stored inputs if any Matrix.setMatrixCallAudioInput(audioDeviceId);
Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']); Matrix.setMatrixCallVideoInput(videoDeviceId);
Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']);
// });
}, },
setAudioInput: function(deviceId) { setAudioInput: function(deviceId) {
UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId); SettingsStore.setValue("webrtc_audioinput", null, "device", deviceId);
Matrix.setMatrixCallAudioInput(deviceId); Matrix.setMatrixCallAudioInput(deviceId);
}, },
setVideoInput: function(deviceId) { setVideoInput: function(deviceId) {
UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId); SettingsStore.setValue("webrtc_videoinput", null, "device", deviceId);
Matrix.setMatrixCallVideoInput(deviceId); Matrix.setMatrixCallVideoInput(deviceId);
}, },
}; };

View File

@ -136,21 +136,4 @@ export default {
disable: disabled, disable: disabled,
}); });
}, },
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));
},
}; };

View File

@ -22,7 +22,6 @@ import React from 'react';
import Matrix from "matrix-js-sdk"; import Matrix from "matrix-js-sdk";
import Analytics from "../../Analytics"; import Analytics from "../../Analytics";
import UserSettingsStore from '../../UserSettingsStore';
import MatrixClientPeg from "../../MatrixClientPeg"; import MatrixClientPeg from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg"; import PlatformPeg from "../../PlatformPeg";
import SdkConfig from "../../SdkConfig"; import SdkConfig from "../../SdkConfig";
@ -44,6 +43,7 @@ import createRoom from "../../createRoom";
import * as UDEHandler from '../../UnknownDeviceErrorHandler'; import * as UDEHandler from '../../UnknownDeviceErrorHandler';
import KeyRequestHandler from '../../KeyRequestHandler'; import KeyRequestHandler from '../../KeyRequestHandler';
import { _t, getCurrentLanguage } from '../../languageHandler'; import { _t, getCurrentLanguage } from '../../languageHandler';
import SettingsStore from "../../settings/SettingsStore";
/** constants for MatrixChat.state.view */ /** constants for MatrixChat.state.view */
const VIEWS = { const VIEWS = {
@ -213,7 +213,7 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
SdkConfig.put(this.props.config); 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 // Used by _viewRoom before getting state from sync
this.firstSyncComplete = false; this.firstSyncComplete = false;

View File

@ -80,41 +80,26 @@ const SIMPLE_SETTINGS = [
// TODO: {Travis} Consider making generic setting handler to support `label` and `fn` optionally (backed by SettingsStore) // TODO: {Travis} Consider making generic setting handler to support `label` and `fn` optionally (backed by SettingsStore)
// TODO: {Travis} Convert
const ANALYTICS_SETTINGS_LABELS = [ const ANALYTICS_SETTINGS_LABELS = [
{ {
id: 'analyticsOptOut', id: 'analyticsOptOut',
label: _td('Opt out of analytics'),
fn: function(checked) { fn: function(checked) {
Analytics[checked ? 'disable' : 'enable'](); Analytics[checked ? 'disable' : 'enable']();
}, },
}, },
]; ];
// TODO: {Travis} Convert
const WEBRTC_SETTINGS_LABELS = [ const WEBRTC_SETTINGS_LABELS = [
{ { id: 'webRtcForceTURN' },
id: 'webRtcForceTURN',
label: _td('Disable Peer-to-Peer for 1:1 calls'),
},
]; ];
// TODO: {Travis} Convert
// Warning: Each "label" string below must be added to i18n/strings/en_EN.json,
// since they will be translated when rendered.
const CRYPTO_SETTINGS_LABELS = [ const CRYPTO_SETTINGS_LABELS = [
{ {
id: 'blacklistUnverifiedDevices', id: 'blacklistUnverifiedDevices',
label: _td('Never send encrypted messages to unverified devices from this device'),
fn: function(checked) { fn: function(checked) {
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked); MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
}, },
}, },
// XXX: this is here for documentation; the actual setting is managed via RoomSettings
// {
// id: 'blacklistUnverifiedDevicesPerRoom'
// label: 'Never send encrypted messages to unverified devices in this room',
// }
]; ];
// Enumerate the available themes, with a nice human text label. // Enumerate the available themes, with a nice human text label.
@ -226,8 +211,6 @@ module.exports = React.createClass({
}); });
this._refreshFromServer(); this._refreshFromServer();
this._localSettings = UserSettingsStore.getLocalSettings();
if (PlatformPeg.get().isElectron()) { if (PlatformPeg.get().isElectron()) {
const {ipcRenderer} = require('electron'); const {ipcRenderer} = require('electron');
@ -298,8 +281,8 @@ module.exports = React.createClass({
if (this._unmounted) return; if (this._unmounted) return;
this.setState({ this.setState({
mediaDevices, mediaDevices,
activeAudioInput: this._localSettings['webrtc_audioinput'], activeAudioInput: SettingsStore.getValueAt("device", 'webrtc_audioinput'),
activeVideoInput: this._localSettings['webrtc_videoinput'], activeVideoInput: SettingsStore.getValueAt("device", 'webrtc_videoinput'),
}); });
}); });
}, },
@ -631,7 +614,7 @@ module.exports = React.createClass({
onLanguageChange: function(newLang) { onLanguageChange: function(newLang) {
if(this.state.language !== newLang) { if(this.state.language !== newLang) {
UserSettingsStore.setLocalSetting('language', newLang); SettingsStore.setValue("language", null, "device", newLang);
this.setState({ this.setState({
language: newLang, language: newLang,
}); });
@ -654,7 +637,7 @@ module.exports = React.createClass({
// TODO: this ought to be a separate component so that we don't need // TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => const onChange = (e) =>
UserSettingsStore.setLocalSetting('autocompleteDelay', + e.target.value); SettingsStore.setValue("autocompleteDelay", null, "device", e.target.value);
return ( return (
<div> <div>
<h3>{ _t("User Interface") }</h3> <h3>{ _t("User Interface") }</h3>
@ -669,7 +652,7 @@ module.exports = React.createClass({
<td> <td>
<input <input
type="number" type="number"
defaultValue={UserSettingsStore.getLocalSetting('autocompleteDelay', 200)} defaultValue={SettingsStore.getValueAt("device", "autocompleteDelay")}
onChange={onChange} onChange={onChange}
/> />
</td> </td>
@ -699,6 +682,7 @@ module.exports = React.createClass({
UserSettingsStore.setUrlPreviewsDisabled(e.target.checked); UserSettingsStore.setUrlPreviewsDisabled(e.target.checked);
}, },
// TODO: {Travis} Make this a component (<CheckboxSetting name='' [label]='' [fn]=() level=''>)
_renderSyncedSetting: function(setting) { _renderSyncedSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need // TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render // to rebind the onChange each time we render
@ -715,11 +699,13 @@ module.exports = React.createClass({
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={setting.id}> <label htmlFor={setting.id}>
{ setting.label || SettingsStore.getDisplayName(setting.id) } { setting.label ? _t(setting.label) : SettingsStore.getDisplayName(setting.id) }
</label> </label>
</div>; </div>;
}, },
// TODO: {Travis} Make this a component (<RadioSetting name='' [label]='' [fn]=() level='' group='theme'>)
// {Travis} Maybe make that part of CheckboxSetting somehow?
_renderThemeSelector: function(setting) { _renderThemeSelector: function(setting) {
// TODO: this ought to be a separate component so that we don't need // TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render // to rebind the onChange each time we render
@ -811,22 +797,23 @@ module.exports = React.createClass({
} else return (<div />); } else return (<div />);
}, },
// TODO: {Travis} Make this a component (<CheckboxSetting name='' [label]='' [fn]=() level=''>)
_renderLocalSetting: function(setting) { _renderLocalSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need // TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render // to rebind the onChange each time we render
const onChange = (e) => { const onChange = (e) => {
UserSettingsStore.setLocalSetting(setting.id, e.target.checked); SettingsStore.setValue(setting.id, null, "device", e.target.checked);
if (setting.fn) setting.fn(e.target.checked); if (setting.fn) setting.fn(e.target.checked);
}; };
return <div className="mx_UserSettings_toggle" key={setting.id}> return <div className="mx_UserSettings_toggle" key={setting.id}>
<input id={setting.id} <input id={setting.id}
type="checkbox" type="checkbox"
defaultChecked={this._localSettings[setting.id]} defaultChecked={SettingsStore.getValueAt("device", setting.id)}
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={setting.id}> <label htmlFor={setting.id}>
{ _t(setting.label) } { setting.label ? _t(setting.label) : SettingsStore.getDisplayName(setting.id) }
</label> </label>
</div>; </div>;
}, },

View File

@ -22,8 +22,8 @@ import { _t, _tJsx } from '../../../languageHandler';
import * as languageHandler from '../../../languageHandler'; import * as languageHandler from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import Login from '../../../Login'; import Login from '../../../Login';
import UserSettingsStore from '../../../UserSettingsStore';
import PlatformPeg from '../../../PlatformPeg'; import PlatformPeg from '../../../PlatformPeg';
import SettingsStore from "../../../settings/SettingsStore";
// For validating phone numbers without country codes // For validating phone numbers without country codes
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/; const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
@ -312,7 +312,7 @@ module.exports = React.createClass({
_onLanguageChange: function(newLang) { _onLanguageChange: function(newLang) {
if(languageHandler.getCurrentLanguage() !== newLang) { if(languageHandler.getCurrentLanguage() !== newLang) {
UserSettingsStore.setLocalSetting('language', newLang); SettingsStore.setValue("language", null, "device", newLang);
PlatformPeg.get().reload(); PlatformPeg.get().reload();
} }
}, },

View File

@ -20,6 +20,7 @@ import React from 'react';
import sdk from '../../../index'; import sdk from '../../../index';
import UserSettingsStore from '../../../UserSettingsStore'; import UserSettingsStore from '../../../UserSettingsStore';
import * as languageHandler from '../../../languageHandler'; import * as languageHandler from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
function languageMatchesSearchQuery(query, language) { function languageMatchesSearchQuery(query, language) {
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true; if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
@ -54,9 +55,10 @@ export default class LanguageDropdown extends React.Component {
// If no value is given, we start with the first // If no value is given, we start with the first
// country selected, but our parent component // country selected, but our parent component
// doesn't know this, therefore we do this. // doesn't know this, therefore we do this.
const _localSettings = UserSettingsStore.getLocalSettings(); // TODO: {Travis} Ensure the default is *not* used for this check. It should try and use the browser.
if (_localSettings.hasOwnProperty('language')) { const language = SettingsStore.getValue("language");
this.props.onOptionChange(_localSettings.language); if (language) {
this.props.onOptionChange(language);
}else { }else {
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser()); const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
this.props.onOptionChange(language); this.props.onOptionChange(language);
@ -95,12 +97,13 @@ export default class LanguageDropdown extends React.Component {
// default value here too, otherwise we need to handle null / undefined // default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propgating // values between mounting and the initial value propgating
// TODO: {Travis} Ensure the default is *not* used for this check. It should try and use the browser.
let language = SettingsStore.getValue("language");
let value = null; let value = null;
const _localSettings = UserSettingsStore.getLocalSettings(); if (language) {
if (_localSettings.hasOwnProperty('language')) { value = this.props.value || language;
value = this.props.value || _localSettings.language;
} else { } else {
const language = navigator.language || navigator.userLanguage; language = navigator.language || navigator.userLanguage;
value = this.props.value || language; value = this.props.value || language;
} }

View File

@ -6,9 +6,9 @@ import isEqual from 'lodash/isEqual';
import sdk from '../../../index'; import sdk from '../../../index';
import type {Completion} from '../../../autocomplete/Autocompleter'; import type {Completion} from '../../../autocomplete/Autocompleter';
import Promise from 'bluebird'; import Promise from 'bluebird';
import UserSettingsStore from '../../../UserSettingsStore';
import {getCompletions} from '../../../autocomplete/Autocompleter'; import {getCompletions} from '../../../autocomplete/Autocompleter';
import SettingsStore from "../../../settings/SettingsStore";
const COMPOSER_SELECTED = 0; const COMPOSER_SELECTED = 0;
@ -66,7 +66,7 @@ export default class Autocomplete extends React.Component {
}); });
return Promise.resolve(null); return Promise.resolve(null);
} }
let autocompleteDelay = UserSettingsStore.getLocalSetting('autocompleteDelay', 200); let autocompleteDelay = SettingsStore.getValue("autocompleteDelay");
// Don't debounce if we are already showing completions // Don't debounce if we are already showing completions
if (this.state.completions.length > 0 || this.state.forceComplete) { if (this.state.completions.length > 0 || this.state.forceComplete) {

View File

@ -23,7 +23,6 @@ import sdk from '../../../index';
import Modal from '../../../Modal'; import Modal from '../../../Modal';
import ObjectUtils from '../../../ObjectUtils'; import ObjectUtils from '../../../ObjectUtils';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import UserSettingsStore from '../../../UserSettingsStore';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
@ -370,7 +369,8 @@ module.exports = React.createClass({
}, },
_isRoomBlacklistUnverified: function() { _isRoomBlacklistUnverified: function() {
const blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom; // TODO: {Travis} Use generic blacklistUnverifiedDevices
const blacklistUnverifiedDevicesPerRoom = SettingsStore.getValue("blacklistUnverifiedDevicesPerRoom");
if (blacklistUnverifiedDevicesPerRoom) { if (blacklistUnverifiedDevicesPerRoom) {
return blacklistUnverifiedDevicesPerRoom[this.props.room.roomId]; return blacklistUnverifiedDevicesPerRoom[this.props.room.roomId];
} }
@ -378,9 +378,10 @@ module.exports = React.createClass({
}, },
_setRoomBlacklistUnverified: function(value) { _setRoomBlacklistUnverified: function(value) {
const blacklistUnverifiedDevicesPerRoom = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevicesPerRoom || {}; // TODO: {Travis} Use generic blacklistUnverifiedDevices
const blacklistUnverifiedDevicesPerRoom = SettingsStore.getValue("blacklistUnverifiedDevicesPerRoom");
blacklistUnverifiedDevicesPerRoom[this.props.room.roomId] = value; blacklistUnverifiedDevicesPerRoom[this.props.room.roomId] = value;
UserSettingsStore.setLocalSetting('blacklistUnverifiedDevicesPerRoom', blacklistUnverifiedDevicesPerRoom); SettingsStore.setValue("blacklistUnverifiedDevicesPerRoom", null, "device", blacklistUnverifiedDevicesPerRoom);
this.props.room.setBlacklistUnverifiedDevices(value); this.props.room.setBlacklistUnverifiedDevices(value);
}, },
@ -591,7 +592,7 @@ module.exports = React.createClass({
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
const roomState = this.props.room.currentState; const roomState = this.props.room.currentState;
const isEncrypted = cli.isRoomEncrypted(this.props.room.roomId); const isEncrypted = cli.isRoomEncrypted(this.props.room.roomId);
const isGlobalBlacklistUnverified = UserSettingsStore.getLocalSettings().blacklistUnverifiedDevices; const isGlobalBlacklistUnverified = SettingsStore.getValue("blacklistUnverifiedDevices");
const isRoomBlacklistUnverified = this._isRoomBlacklistUnverified(); const isRoomBlacklistUnverified = this._isRoomBlacklistUnverified();
const settings = const settings =

View File

@ -19,8 +19,7 @@ import request from 'browser-request';
import counterpart from 'counterpart'; import counterpart from 'counterpart';
import Promise from 'bluebird'; import Promise from 'bluebird';
import React from 'react'; import React from 'react';
import SettingsStore from "./settings/SettingsStore";
import UserSettingsStore from './UserSettingsStore';
const i18nFolder = 'i18n/'; const i18nFolder = 'i18n/';
@ -168,7 +167,7 @@ export function setLanguage(preferredLangs) {
}).then((langData) => { }).then((langData) => {
counterpart.registerTranslations(langToUse, langData); counterpart.registerTranslations(langToUse, langData);
counterpart.setLocale(langToUse); counterpart.setLocale(langToUse);
UserSettingsStore.setLocalSetting('language', langToUse); SettingsStore.setValue("language", null, "device", langToUse);
console.log("set language to " + langToUse); console.log("set language to " + langToUse);
// Set 'en' as fallback language: // Set 'en' as fallback language:

View File

@ -157,6 +157,41 @@ const SETTINGS = {
supportedLevels: LEVELS_PRESET_ACCOUNT, supportedLevels: LEVELS_PRESET_ACCOUNT,
default: "light", default: "light",
}, },
"webRtcForceTURN": {
supportedLevels: ['device'],
default: false,
displayName: _td('Disable Peer-to-Peer for 1:1 calls'),
},
"webrtc_audioinput": {
supportedLevels: ['device'],
},
"webrtc_videoinput": {
supportedLevels: ['device'],
},
"language": {
supportedLevels: ['device'],
default: "en"
},
"analyticsOptOut": {
supportedLevels: ['device'],
default: false,
displayName: _td('Opt out of analytics'),
},
"autocompleteDelay": {
supportedLevels: ['device'],
default: 200,
},
"blacklistUnverifiedDevicesPerRoom": {
// TODO: {Travis} Write a migration path to support blacklistUnverifiedDevices
supportedLevels: ['device'],
default: {},
},
"blacklistUnverifiedDevices": {
// TODO: {Travis} Write a migration path to support blacklistUnverifiedDevices
supportedLevels: ['device'],
default: false,
label: _td('Never send encrypted messages to unverified devices from this device'),
}
}; };
// Convert the above into simpler formats for the handlers // Convert the above into simpler formats for the handlers