From f4f30a327410a0a0507e3b750ece0e968e043fb2 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 14 Sep 2020 22:27:40 -0600 Subject: [PATCH] Introduce a concept of UI features, using it for URL previews at first Fixes https://github.com/vector-im/element-web/issues/15176 This is effectively the base for all of https://github.com/vector-im/element-web/issues/15185 --- docs/settings.md | 13 +++++- .../tabs/room/GeneralRoomSettingsTab.js | 17 +++++-- .../tabs/user/PreferencesUserSettingsTab.js | 5 +++ src/i18n/strings/en_EN.json | 2 +- src/settings/Settings.ts | 12 +++++ src/settings/UIFeature.ts | 20 +++++++++ .../controllers/UIFeatureController.ts | 45 +++++++++++++++++++ 7 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/settings/UIFeature.ts create mode 100644 src/settings/controllers/UIFeatureController.ts 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")} +
+ +
+ ; + if (!SettingsStore.getValue(UIFeature.URLPreviews)) { + urlPreviewSettings = null; + } + return (
{_t("General")}
@@ -82,10 +94,7 @@ export default class GeneralRoomSettingsTab extends React.Component { relatedGroupsEvent={groupsEvent} />
- {_t("URL Previews")} -
- -
+ {urlPreviewSettings} {_t("Leave room")}
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 + } +}