Merge pull request #5124 from matrix-org/travis/settings-v3

Settings v3: Feature flag changes
This commit is contained in:
Travis Ralston 2020-08-19 12:00:48 -06:00 committed by GitHub
commit c9d98a1d19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 85 additions and 175 deletions

View file

@ -197,7 +197,7 @@ export default class FromWidgetPostMessageApi {
const integId = (data && data.integId) ? data.integId : null;
// TODO: Open the right integration manager for the widget
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(
MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()),
`type_${integType}`,

View file

@ -378,7 +378,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
const tagPanel = !this.state.showTagPanel ? null : (
<div className="mx_LeftPanel_tagPanelContainer">
<TagPanel/>
{SettingsStore.isFeatureEnabled("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
{SettingsStore.getValue("feature_custom_tags") ? <CustomRoomTagPanel /> : null}
</div>
);

View file

@ -53,7 +53,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) {
throw new Error("Cannot use MemberStatusMessageAvatar on anyone but the logged in user");
}
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
if (!SettingsStore.getValue("feature_custom_status")) {
return;
}
const { user } = this.props.member;
@ -105,7 +105,7 @@ export default class MemberStatusMessageAvatar extends React.Component {
resizeMethod={this.props.resizeMethod}
/>;
if (!SettingsStore.isFeatureEnabled("feature_custom_status")) {
if (!SettingsStore.getValue("feature_custom_status")) {
return avatar;
}

View file

@ -81,7 +81,7 @@ export default createReactClass({
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
if (!SettingsStore.isFeatureEnabled("feature_pinning")) canPin = false;
if (!SettingsStore.getValue("feature_pinning")) canPin = false;
this.setState({canRedact, canPin});
},

View file

@ -87,7 +87,7 @@ export default class RoomSettingsDialog extends React.Component {
<NotificationSettingsTab roomId={this.props.roomId} />,
));
if (SettingsStore.isFeatureEnabled("feature_bridge_state")) {
if (SettingsStore.getValue("feature_bridge_state")) {
tabs.push(new Tab(
ROOM_BRIDGES_TAB,
_td("Bridges"),

View file

@ -54,7 +54,7 @@ export default class UserSettingsDialog extends React.Component {
super();
this.state = {
mjolnirEnabled: SettingsStore.isFeatureEnabled("feature_mjolnir"),
mjolnirEnabled: SettingsStore.getValue("feature_mjolnir"),
};
}
@ -116,7 +116,7 @@ export default class UserSettingsDialog extends React.Component {
"mx_UserSettingsDialog_securityIcon",
<SecurityUserSettingsTab closeSettingsFn={this.props.onFinished} />,
));
if (SdkConfig.get()['showLabsSettings'] || SettingsStore.getLabsFeatures().length > 0) {
if (SdkConfig.get()['showLabsSettings']) {
tabs.push(new Tab(
USER_LABS_TAB,
_td("Labs"),

View file

@ -311,7 +311,7 @@ export default class AppTile extends React.Component {
this.props.onEditClick();
} else {
// TODO: Open the right manager for the widget
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(
this.props.room,
'type_' + this.props.app.type,

View file

@ -28,7 +28,7 @@ export default createReactClass({
const imgClass = this.props.imgClassName || "";
let imageSource;
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
if (SettingsStore.getValue('feature_new_spinner')) {
imageSource = require("../../../../res/img/spinner.svg");
} else {
imageSource = require("../../../../res/img/spinner.gif");

View file

@ -34,7 +34,7 @@ export default class ManageIntegsButton extends React.Component {
if (!managers.hasManager()) {
managers.openNoManagerDialog();
} else {
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
if (SettingsStore.getValue("feature_many_integration_managers")) {
managers.openAll(this.props.room);
} else {
managers.getPrimaryManager().open(this.props.room);

View file

@ -22,7 +22,7 @@ import SettingsStore from "../../../settings/SettingsStore";
const Spinner = ({w = 32, h = 32, imgClassName, message}) => {
let imageSource;
if (SettingsStore.isFeatureEnabled('feature_new_spinner')) {
if (SettingsStore.getValue('feature_new_spinner')) {
imageSource = require("../../../../res/img/spinner.svg");
} else {
imageSource = require("../../../../res/img/spinner.gif");

View file

@ -95,7 +95,7 @@ export default createReactClass({
}
}
if (SettingsStore.isFeatureEnabled("feature_mjolnir")) {
if (SettingsStore.getValue("feature_mjolnir")) {
const key = `mx_mjolnir_render_${this.props.mxEvent.getRoomId()}__${this.props.mxEvent.getId()}`;
const allowRender = localStorage.getItem(key) === "true";

View file

@ -1428,7 +1428,7 @@ const UserInfoHeader = ({onClose, member, e2eStatus}) => {
presenceLastActiveAgo = member.user.lastActiveAgo;
presenceCurrentlyActive = member.user.currentlyActive;
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
if (SettingsStore.getValue("feature_custom_status")) {
statusMessage = member.user._unstable_statusMessage;
}
}

View file

@ -130,7 +130,7 @@ export default createReactClass({
},
_launchManageIntegrations: function() {
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll();
} else {
IntegrationManagers.sharedInstance().getPrimaryManager().open(this.props.room, 'add_integ');

View file

@ -104,7 +104,7 @@ export default createReactClass({
},
_rateLimitedUpdate: new RateLimitedFunc(function() {
if (SettingsStore.isFeatureEnabled("feature_state_counters")) {
if (SettingsStore.getValue("feature_state_counters")) {
this.setState({counters: this._computeCounters()});
}
}, 500),
@ -112,7 +112,7 @@ export default createReactClass({
_computeCounters: function() {
let counters = [];
if (this.props.room && SettingsStore.isFeatureEnabled("feature_state_counters")) {
if (this.props.room && SettingsStore.getValue("feature_state_counters")) {
const stateEvs = this.props.room.currentState.getStateEvents('re.jki.counter');
stateEvs.sort((a, b) => {
return a.getStateKey() < b.getStateKey();
@ -206,7 +206,7 @@ export default createReactClass({
/>;
let stateViews = null;
if (this.state.counters && SettingsStore.isFeatureEnabled("feature_state_counters")) {
if (this.state.counters && SettingsStore.getValue("feature_state_counters")) {
let counters = [];
this.state.counters.forEach((counter, idx) => {

View file

@ -50,7 +50,7 @@ export default createReactClass({
componentDidMount() {
const cli = MatrixClientPeg.get();
if (SettingsStore.isFeatureEnabled("feature_custom_status")) {
if (SettingsStore.getValue("feature_custom_status")) {
const { user } = this.props.member;
if (user) {
user.on("User._unstable_statusMessage", this._onStatusMessageCommitted);
@ -209,7 +209,7 @@ export default createReactClass({
const presenceState = member.user ? member.user.presence : null;
let statusMessage = null;
if (member.user && SettingsStore.isFeatureEnabled("feature_custom_status")) {
if (member.user && SettingsStore.getValue("feature_custom_status")) {
statusMessage = this.state.statusMessage;
}

View file

@ -226,7 +226,7 @@ export default createReactClass({
title={_t("Settings")} />;
}
if (this.props.onPinnedClick && SettingsStore.isFeatureEnabled('feature_pinning')) {
if (this.props.onPinnedClick && SettingsStore.getValue('feature_pinning')) {
let pinsIndicator = null;
if (this._hasUnreadPins()) {
pinsIndicator = (<div className="mx_RoomHeader_pinsIndicator mx_RoomHeader_pinsIndicatorUnread" />);

View file

@ -363,7 +363,7 @@ export default class Stickerpicker extends React.Component {
*/
_launchManageIntegrations() {
// TODO: Open the right integration manager for the widget
if (SettingsStore.isFeatureEnabled("feature_many_integration_managers")) {
if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll(
this.props.room,
`type_${WidgetType.STICKERPICKER.preferred}`,

View file

@ -237,7 +237,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
}
let customThemeForm: JSX.Element;
if (SettingsStore.isFeatureEnabled("feature_custom_themes")) {
if (SettingsStore.getValue("feature_custom_themes")) {
let messageElement = null;
if (this.state.customThemeMessage.text) {
if (this.state.customThemeMessage.isError) {

View file

@ -28,14 +28,15 @@ export class LabsSettingToggle extends React.Component {
};
_onChange = async (checked) => {
await SettingsStore.setFeatureEnabled(this.props.featureId, checked);
await SettingsStore.setValue(this.props.featureId, null, SettingLevel.DEVICE, checked);
this.forceUpdate();
};
render() {
const label = SettingsStore.getDisplayName(this.props.featureId);
const value = SettingsStore.isFeatureEnabled(this.props.featureId);
return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} />;
const value = SettingsStore.getValue(this.props.featureId);
const canChange = SettingsStore.canSetValue(this.props.featureId, null, SettingLevel.DEVICE);
return <LabelledToggleSwitch value={value} label={label} onChange={this._onChange} disabled={!canChange} />;
}
}
@ -46,7 +47,7 @@ export default class LabsUserSettingsTab extends React.Component {
render() {
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
const flags = SettingsStore.getLabsFeatures().map(f => <LabsSettingToggle featureId={f} key={f} />);
const flags = SettingsStore.getFeatureSettingNames().map(f => <LabsSettingToggle featureId={f} key={f} />);
return (
<div className="mx_SettingsTab">
<div className="mx_SettingsTab_heading">{_t("Labs")}</div>

View file

@ -36,11 +36,11 @@ export const useSettingValue = (settingName: string, roomId: string = null, excl
// Hook to fetch whether a feature is enabled and dynamically update when that changes
export const useFeatureEnabled = (featureName: string, roomId: string = null) => {
const [enabled, setEnabled] = useState(SettingsStore.isFeatureEnabled(featureName, roomId));
const [enabled, setEnabled] = useState(SettingsStore.getValue(featureName, roomId));
useEffect(() => {
const ref = SettingsStore.watchSetting(featureName, roomId, () => {
setEnabled(SettingsStore.isFeatureEnabled(featureName, roomId));
setEnabled(SettingsStore.getValue(featureName, roomId));
});
// clean-up
return () => {

View file

@ -123,7 +123,7 @@ async function collectBugReport(opts: IOpts = {}, gzipLogs = true) {
}
// add labs options
const enabledLabs = SettingsStore.getLabsFeatures().filter(f => SettingsStore.isFeatureEnabled(f));
const enabledLabs = SettingsStore.getFeatureSettingNames().filter(f => SettingsStore.getValue(f));
if (enabledLabs.length) {
body.append('enabled_labs', enabledLabs.join(', '));
}

View file

@ -23,7 +23,6 @@ import AccountSettingsHandler from "./handlers/AccountSettingsHandler";
import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
import { _t } from '../languageHandler';
import SdkConfig from "../SdkConfig";
import dis from '../dispatcher/dispatcher';
import { ISetting, SETTINGS } from "./Settings";
import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
@ -53,7 +52,7 @@ const LEVEL_HANDLERS = {
[SettingLevel.ROOM_ACCOUNT]: new RoomAccountSettingsHandler(defaultWatchManager),
[SettingLevel.ACCOUNT]: new AccountSettingsHandler(defaultWatchManager),
[SettingLevel.ROOM]: new RoomSettingsHandler(defaultWatchManager),
[SettingLevel.CONFIG]: new ConfigSettingsHandler(),
[SettingLevel.CONFIG]: new ConfigSettingsHandler(featureNames),
[SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
};
@ -124,6 +123,14 @@ export default class SettingsStore {
// Counter used for generation of watcher IDs
private static watcherCount = 1;
/**
* Gets all the feature-style setting names.
* @returns {string[]} The names of the feature settings.
*/
public static getFeatureSettingNames(): string[] {
return Object.keys(SETTINGS).filter(n => SettingsStore.isFeature(n));
}
/**
* Watches for changes in a particular setting. This is done without any local echo
* wrapping and fires whenever a change is detected in a setting's value, at any level.
@ -240,19 +247,6 @@ export default class SettingsStore {
return _t(displayName as string);
}
/**
* Returns a list of all available labs feature names
* @returns {string[]} The list of available feature names
*/
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");
}
/**
* Determines if a setting is also a feature.
* @param {string} settingName The setting to look up.
@ -263,39 +257,6 @@ export default class SettingsStore {
return SETTINGS[settingName].isFeature;
}
/**
* Determines if a given feature is enabled. The feature given must be a known
* feature.
* @param {string} settingName The name of the setting that is a feature.
* @param {String} roomId The optional room ID to validate in, may be null.
* @return {boolean} True if the feature is enabled, false otherwise
*/
public static isFeatureEnabled(settingName: string, roomId: string = null) {
if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature");
}
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.
*/
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.");
}
if (!SettingsStore.isFeature(settingName)) {
throw new Error("Setting " + settingName + " is not a feature");
}
return SettingsStore.setValue(settingName, null, SettingLevel.DEVICE, value);
}
/**
* 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.
@ -346,13 +307,6 @@ export default class SettingsStore {
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);
if (configValue === "enable") return true;
if (configValue === "disable") return false;
// else let it fall through the default process
}
const handlers = SettingsStore.getHandlers(settingName);
// Check if we need to invert the setting at all. Do this after we get the setting
@ -480,6 +434,12 @@ export default class SettingsStore {
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
}
// When features are specified in the config.json, we force them as enabled or disabled.
if (SettingsStore.isFeature(settingName)) {
const configVal = SettingsStore.getValueAt(SettingLevel.CONFIG, settingName, roomId, true, true);
if (configVal === true || configVal === false) return false;
}
const handler = SettingsStore.getHandler(settingName, level);
if (!handler) return false;
return handler.canSetValue(settingName, roomId);
@ -611,24 +571,6 @@ export default class SettingsStore {
return handlers;
}
private static getFeatureState(settingName: string): LabsFeatureState {
const featuresConfig = SdkConfig.get()['features'];
const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag
let featureState = enableLabs ? "labs" : "disable";
if (featuresConfig && featuresConfig[settingName] !== undefined) {
featureState = featuresConfig[settingName];
}
const allowedStates = ['enable', 'disable', 'labs'];
if (!allowedStates.includes(featureState)) {
console.warn("Feature state '" + featureState + "' is invalid for " + settingName);
featureState = "disable"; // to prevent accidental features.
}
return featureState;
}
}
// For debugging purposes

View file

@ -24,9 +24,24 @@ import {isNullOrUndefined} from "matrix-js-sdk/src/utils";
* roomId parameter.
*/
export default class ConfigSettingsHandler extends SettingsHandler {
public constructor(private featureNames: string[]) {
super();
}
public getValue(settingName: string, roomId: string): any {
const config = SdkConfig.get() || {};
if (this.featureNames.includes(settingName)) {
const labsConfig = config["features"] || {};
const val = labsConfig[settingName];
if (isNullOrUndefined(val)) return null; // no definition at this level
if (val === true || val === false) return val; // new style: mapped as a boolean
if (val === "enable") return true; // backwards compat
if (val === "disable") return false; // backwards compat
if (val === "labs") return null; // backwards compat, no override
return null; // fallback in the case of invalid input
}
// Special case themes
if (settingName === "theme") {
return config["default_theme"];

View file

@ -137,7 +137,7 @@ class CustomRoomTagStore extends EventEmitter {
}
_getUpdatedTags() {
if (!SettingsStore.isFeatureEnabled("feature_custom_tags")) {
if (!SettingsStore.getValue("feature_custom_tags")) {
return {}; // none
}

View file

@ -136,7 +136,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
}
private async readAndCacheSettingsFromStore() {
const tagsEnabled = SettingsStore.isFeatureEnabled("feature_custom_tags");
const tagsEnabled = SettingsStore.getValue("feature_custom_tags");
await this.updateState({
tagsEnabled,
});

View file

@ -24,8 +24,8 @@ import DMRoomMap from "../../../utils/DMRoomMap";
export class ReactionEventPreview implements IPreview {
public getTextFor(event: MatrixEvent, tagId?: TagID): string {
const showDms = SettingsStore.isFeatureEnabled("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.isFeatureEnabled("feature_roomlist_preview_reactions_all");
const showDms = SettingsStore.getValue("feature_roomlist_preview_reactions_dms");
const showAll = SettingsStore.getValue("feature_roomlist_preview_reactions_all");
if (!showAll && (!showDms || DMRoomMap.shared().getUserIdForRoomId(event.getRoomId()))) return null;