diff --git a/docs/settings.md b/docs/settings.md
index 4172c72c15..891877a57a 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -120,6 +120,18 @@ Call `SettingsStore.getValue()` as you would for any other setting.
Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`.
+### A note on UI features
+
+UI features are a different concept to plain features. Instead of being representative of unstable or
+unpredicatable behaviour, they are logical chunks of UI which can be disabled by deployments for ease
+of understanding with users. They are simply represented as boring settings with a convention of being
+named as `UIFeature.$location` where `$location` is a rough descriptor of what is toggled, such as
+`URLPreviews` or `Communities`.
+
+UI features also tend to have their own setting controller (see below) to manipulate settings which might
+be affected by the UI feature being disabled. For example, if URL previews are disabled as a UI feature
+then the URL preview options will use the `UIFeatureController` to ensure they remain disabled while the
+UI feature is disabled.
## Setting controllers
@@ -226,4 +238,3 @@ In practice, handlers which rely on remote changes (account data, room events, e
generalized `WatchManager` - a class specifically designed to deduplicate the logic of managing watchers. The handlers
which are localized to the local client (device) generally just trigger the `WatchManager` when they manipulate the
setting themselves as there's nothing to really 'watch'.
-
diff --git a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
index 1f12396413..90eb60e632 100644
--- a/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
+++ b/src/components/views/settings/tabs/room/GeneralRoomSettingsTab.js
@@ -22,6 +22,8 @@ import * as sdk from "../../../../..";
import AccessibleButton from "../../../elements/AccessibleButton";
import dis from "../../../../../dispatcher/dispatcher";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
+import SettingsStore from "../../../../../settings/SettingsStore";
+import {UIFeature} from "../../../../../settings/UIFeature";
export default class GeneralRoomSettingsTab extends React.Component {
static propTypes = {
@@ -61,6 +63,16 @@ export default class GeneralRoomSettingsTab extends React.Component {
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
+ let urlPreviewSettings = <>
+ {_t("URL Previews")}
+
diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
index a77815a68c..0da8129568 100644
--- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.js
@@ -23,6 +23,7 @@ import Field from "../../../elements/Field";
import * as sdk from "../../../../..";
import PlatformPeg from "../../../../../PlatformPeg";
import {SettingLevel} from "../../../../../settings/SettingLevel";
+import {UIFeature} from "../../../../../settings/UIFeature";
export default class PreferencesUserSettingsTab extends React.Component {
static ROOM_LIST_SETTINGS = [
@@ -138,6 +139,10 @@ export default class PreferencesUserSettingsTab extends React.Component {
};
_renderGroup(settingIds) {
+ if (!SettingsStore.getValue(UIFeature.URLPreviews)) {
+ settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled');
+ }
+
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
return settingIds.map(i =>
);
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 93781160ce..1249345e7c 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -946,8 +946,8 @@
"This room is bridging messages to the following platforms.
Learn more.": "This room is bridging messages to the following platforms.
Learn more.",
"This room isn’t bridging messages to any platforms.
Learn more.": "This room isn’t bridging messages to any platforms.
Learn more.",
"Bridges": "Bridges",
- "Room Addresses": "Room Addresses",
"URL Previews": "URL Previews",
+ "Room Addresses": "Room Addresses",
"Uploaded sound": "Uploaded sound",
"Sounds": "Sounds",
"Notification sound": "Notification sound",
diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts
index 9e0f36b1ba..03d8c8b136 100644
--- a/src/settings/Settings.ts
+++ b/src/settings/Settings.ts
@@ -32,6 +32,8 @@ import UseSystemFontController from './controllers/UseSystemFontController';
import { SettingLevel } from "./SettingLevel";
import SettingController from "./controllers/SettingController";
import { RightPanelPhases } from "../stores/RightPanelStorePhases";
+import UIFeatureController from "./controllers/UIFeatureController";
+import { UIFeature } from "./UIFeature";
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
const LEVELS_ROOM_SETTINGS = [
@@ -69,6 +71,10 @@ const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = [
SettingLevel.DEVICE,
SettingLevel.CONFIG,
];
+const LEVELS_UI_FEATURE = [
+ SettingLevel.CONFIG,
+ // in future we might have a .well-known level or something
+];
export interface ISetting {
// Must be set to true for features. Default is 'false'.
@@ -447,6 +453,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
"room": _td("Enable URL previews by default for participants in this room"),
},
default: true,
+ controller: new UIFeatureController(UIFeature.URLPreviews),
},
"urlPreviewsEnabled_e2ee": {
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
@@ -454,6 +461,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
"room-account": _td("Enable URL previews for this room (only affects you)"),
},
default: false,
+ controller: new UIFeatureController(UIFeature.URLPreviews),
},
"roomColor": {
supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM,
@@ -611,4 +619,8 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
default: {},
},
+ [UIFeature.URLPreviews]: {
+ supportedLevels: LEVELS_UI_FEATURE,
+ default: true,
+ },
};
diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts
new file mode 100644
index 0000000000..3ca0c67484
--- /dev/null
+++ b/src/settings/UIFeature.ts
@@ -0,0 +1,20 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// see settings.md for documentation on conventions
+export enum UIFeature {
+ URLPreviews = "UIFeature.URLPreviews",
+}
diff --git a/src/settings/controllers/UIFeatureController.ts b/src/settings/controllers/UIFeatureController.ts
new file mode 100644
index 0000000000..ed6598a6e8
--- /dev/null
+++ b/src/settings/controllers/UIFeatureController.ts
@@ -0,0 +1,45 @@
+/*
+Copyright 2020 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import SettingController from "./SettingController";
+import { SettingLevel } from "../SettingLevel";
+import SettingsStore from "../SettingsStore";
+
+/**
+ * Enforces that a boolean setting cannot be enabled if the corresponding
+ * UI feature is disabled. If the UI feature is enabled, the setting value
+ * is unchanged.
+ *
+ * Settings using this controller are assumed to return `false` when disabled.
+ */
+export default class UIFeatureController extends SettingController {
+ public constructor(private uiFeatureName: string) {
+ super();
+ }
+
+ public getValueOverride(
+ level: SettingLevel,
+ roomId: string,
+ calculatedValue: any,
+ calculatedAtLevel: SettingLevel,
+ ): any {
+ if (!SettingsStore.getValue(this.uiFeatureName)) {
+ // per the docs: we force a disabled state when the feature isn't active
+ return false;
+ }
+ return null; // no override
+ }
+}