mirror of https://github.com/vector-im/riot-web
Support labs features
Signed-off-by: Travis Ralston <travpc@gmail.com>pull/21833/head
parent
7dda5e9196
commit
bf815f4be9
|
@ -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);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue