Support labs features

Signed-off-by: Travis Ralston <travpc@gmail.com>
pull/21833/head
Travis Ralston 2017-10-28 20:21:34 -06:00
parent 7dda5e9196
commit bf815f4be9
7 changed files with 68 additions and 106 deletions

View File

@ -25,50 +25,7 @@ import SdkConfig from './SdkConfig';
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage. * TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
*/ */
const FEATURES = [
{
id: 'feature_groups',
name: _td("Communities"),
},
{
id: 'feature_pinning',
name: _td("Message Pinning"),
},
];
export default { 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() { loadProfileInfo: function() {
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
return cli.getProfileInfo(cli.credentials.userId); return cli.getProfileInfo(cli.credentials.userId);
@ -213,37 +170,4 @@ export default {
// FIXME: handle errors // FIXME: handle errors
localStorage.setItem('mx_local_settings', JSON.stringify(settings)); 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);
},
}; };

View File

@ -15,6 +15,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import SettingsStore from "../../settings/SettingsStore";
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const sdk = require('../../index'); const sdk = require('../../index');
@ -934,11 +936,11 @@ module.exports = React.createClass({
_renderLabs: function() { _renderLabs: function() {
const features = []; const features = [];
UserSettingsStore.getLabsFeatures().forEach((featureId) => { SettingsStore.getLabsFeatures().forEach((featureId) => {
// 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.setFeatureEnabled(featureId, e.target.checked); SettingsStore.setFeatureEnabled(featureId, e.target.checked);
this.forceUpdate(); this.forceUpdate();
}; };
@ -948,10 +950,10 @@ module.exports = React.createClass({
type="checkbox" type="checkbox"
id={featureId} id={featureId}
name={featureId} name={featureId}
defaultChecked={UserSettingsStore.isFeatureEnabled(featureId)} defaultChecked={SettingsStore.isFeatureEnabled(featureId)}
onChange={onChange} onChange={onChange}
/> />
<label htmlFor={featureId}>{ UserSettingsStore.translatedNameForFeature(featureId) }</label> <label htmlFor={featureId}>{ SettingsStore.getDisplayName(featureId) }</label>
</div>); </div>);
}); });

View File

@ -19,9 +19,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {MatrixClient} from 'matrix-js-sdk'; import {MatrixClient} from 'matrix-js-sdk';
import UserSettingsStore from '../../../UserSettingsStore';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
class FlairAvatar extends React.Component { class FlairAvatar extends React.Component {
@ -79,7 +79,7 @@ export default class Flair extends React.Component {
componentWillMount() { componentWillMount() {
this._unmounted = false; this._unmounted = false;
if (UserSettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) { if (SettingsStore.isFeatureEnabled('feature_groups') && FlairStore.groupSupport()) {
this._generateAvatars(); this._generateAvatars();
} }
this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents); this.context.matrixClient.on('RoomState.events', this.onRoomStateEvents);

View File

@ -31,7 +31,7 @@ import linkifyMatrix from '../../../linkify-matrix';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import ManageIntegsButton from '../elements/ManageIntegsButton'; import ManageIntegsButton from '../elements/ManageIntegsButton';
import {CancelButton} from './SimpleRoomHeader'; import {CancelButton} from './SimpleRoomHeader';
import UserSettingsStore from "../../../UserSettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
linkifyMatrix(linkify); linkifyMatrix(linkify);
@ -304,7 +304,7 @@ module.exports = React.createClass({
</AccessibleButton>; </AccessibleButton>;
} }
if (this.props.onPinnedClick && UserSettingsStore.isFeatureEnabled('feature_pinning')) { if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
pinnedEventsButton = pinnedEventsButton =
<AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}> <AccessibleButton className="mx_RoomHeader_button" onClick={this.props.onPinnedClick} title={_t("Pinned Messages")}>
<TintableSvg src="img/icons-pin.svg" width="16" height="16" /> <TintableSvg src="img/icons-pin.svg" width="16" height="16" />

View File

@ -25,6 +25,7 @@ import ObjectUtils from '../../../ObjectUtils';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
import UserSettingsStore from '../../../UserSettingsStore'; import UserSettingsStore from '../../../UserSettingsStore';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import SettingsStore from "../../../settings/SettingsStore";
// parse a string as an integer; if the input is undefined, or cannot be parsed // parse a string as an integer; if the input is undefined, or cannot be parsed
@ -671,7 +672,7 @@ module.exports = React.createClass({
const self = this; const self = this;
let relatedGroupsSection; let relatedGroupsSection;
if (UserSettingsStore.isFeatureEnabled('feature_groups')) { if (SettingsStore.isFeatureEnabled('feature_groups')) {
relatedGroupsSection = <RelatedGroupSettings ref="related_groups" relatedGroupsSection = <RelatedGroupSettings ref="related_groups"
roomId={this.props.room.roomId} roomId={this.props.room.roomId}
canSetRelatedGroups={roomState.mayClientSendStateEvent("m.room.related_groups", cli)} canSetRelatedGroups={roomState.mayClientSendStateEvent("m.room.related_groups", cli)}

View File

@ -46,7 +46,8 @@ export default class DeviceSettingsHandler extends SettingsHandler {
setValue(settingName, roomId, newValue) { setValue(settingName, roomId, newValue) {
if (this._featureNames.includes(settingName)) { if (this._featureNames.includes(settingName)) {
return Promise.resolve(this._writeFeature(settingName)); this._writeFeature(settingName, newValue);
return Promise.resolve();
} }
if (newValue === null) { if (newValue === null) {

View File

@ -36,23 +36,34 @@ const LEVELS_PRESET_ACCOUNT = ['device', 'account'];
const LEVELS_PRESET_FEATURE = ['device']; const LEVELS_PRESET_FEATURE = ['device'];
const SETTINGS = { const SETTINGS = {
"my-setting": { // EXAMPLE SETTING:
isFeature: false, // optional // "my-setting": {
displayName: _td("Cool Name"), // isFeature: false, // optional
supportedLevels: [ // displayName: _td("Cool Name"),
// The order does not matter. // supportedLevels: [
// // The order does not matter.
"device", // Affects the current device only //
"room-device", // Affects the current room on the current device // "device", // Affects the current device only
"room-account", // Affects the current room for the current account // "room-device", // Affects the current room on the current device
"account", // Affects the current account // "room-account", // Affects the current room for the current account
"room", // Affects the current room (controlled by room admins) // "account", // Affects the current account
// "room", // Affects the current room (controlled by room admins)
// "default" and "config" are always supported and do not get listed here. //
], // // "default" and "config" are always supported and do not get listed here.
defaults: { // ],
your: "value", // default: {
}, // your: "value",
// },
// },
"feature_groups": {
isFeature: true,
displayName: _td("Communities"),
supportedLevels: LEVELS_PRESET_FEATURE,
},
"feature_pinning": {
isFeature: true,
displayName: _td("Message Pinning"),
supportedLevels: LEVELS_PRESET_FEATURE,
}, },
// TODO: Populate this // TODO: Populate this
@ -62,7 +73,7 @@ const SETTINGS = {
const defaultSettings = {}; const defaultSettings = {};
const featureNames = []; const featureNames = [];
for (const key of Object.keys(SETTINGS)) { for (const key of Object.keys(SETTINGS)) {
defaultSettings[key] = SETTINGS[key].defaults; defaultSettings[key] = SETTINGS[key].default;
if (SETTINGS[key].isFeature) featureNames.push(key); if (SETTINGS[key].isFeature) featureNames.push(key);
} }
@ -110,6 +121,19 @@ export default class SettingsStore {
return _t(SETTINGS[settingName].displayName); return _t(SETTINGS[settingName].displayName);
} }
/**
* Returns a list of all available labs feature names
* @returns {string[]} The list of available feature names
*/
static getLabsFeatures() {
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");
}
/** /**
* Determines if a setting is also a feature. * Determines if a setting is also a feature.
* @param {string} settingName The setting to look up. * @param {string} settingName The setting to look up.
@ -135,6 +159,16 @@ export default class SettingsStore {
return SettingsStore.getValue(settingName, roomId); return SettingsStore.getValue(settingName, roomId);
} }
/**
* Sets a feature as enabled or disabled on the current device.
* @param {string} settingName The name of the setting.
* @param {boolean} value True to enable the feature, false otherwise.
* @returns {Promise} Resolves when the setting has been set.
*/
static setFeatureEnabled(settingName, value) {
return SettingsStore.setValue(settingName, null, "device", value);
}
/** /**
* Gets the value of a setting. The room ID is optional if the setting is not to * Gets the value of a setting. The room ID is optional if the setting is not to
* be applied to any particular room, otherwise it should be supplied. * be applied to any particular room, otherwise it should be supplied.
@ -228,7 +262,7 @@ export default class SettingsStore {
if (!SETTINGS[settingName]) return {}; if (!SETTINGS[settingName]) return {};
const handlers = {}; const handlers = {};
for (let level of SETTINGS[settingName].supportedLevels) { for (const level of SETTINGS[settingName].supportedLevels) {
if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level); if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level);
handlers[level] = LEVEL_HANDLERS[level]; handlers[level] = LEVEL_HANDLERS[level];
} }
@ -246,7 +280,7 @@ export default class SettingsStore {
} }
const allowedStates = ['enable', 'disable', 'labs']; const allowedStates = ['enable', 'disable', 'labs'];
if (!allowedStates.contains(featureState)) { if (!allowedStates.includes(featureState)) {
console.warn("Feature state '" + featureState + "' is invalid for " + settingName); console.warn("Feature state '" + featureState + "' is invalid for " + settingName);
featureState = "disable"; // to prevent accidental features. featureState = "disable"; // to prevent accidental features.
} }