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
This commit is contained in:
Travis Ralston 2020-09-14 22:27:40 -06:00
parent ab91ce4b2d
commit f4f30a3274
7 changed files with 108 additions and 6 deletions

View file

@ -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'.

View file

@ -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 = <>
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
<div className='mx_SettingsTab_section'>
<UrlPreviewSettings room={room} />
</div>
</>;
if (!SettingsStore.getValue(UIFeature.URLPreviews)) {
urlPreviewSettings = null;
}
return (
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
<div className="mx_SettingsTab_heading">{_t("General")}</div>
@ -82,10 +94,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
relatedGroupsEvent={groupsEvent} />
</div>
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
<div className='mx_SettingsTab_section'>
<UrlPreviewSettings room={room} />
</div>
{urlPreviewSettings}
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
<div className='mx_SettingsTab_section'>

View file

@ -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 => <SettingsFlag key={i} name={i} level={SettingLevel.ACCOUNT} />);
}

View file

@ -946,8 +946,8 @@
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "This room is bridging messages to the following platforms. <a>Learn more.</a>",
"This room isnt bridging messages to any platforms. <a>Learn more.</a>": "This room isnt bridging messages to any platforms. <a>Learn more.</a>",
"Bridges": "Bridges",
"Room Addresses": "Room Addresses",
"URL Previews": "URL Previews",
"Room Addresses": "Room Addresses",
"Uploaded sound": "Uploaded sound",
"Sounds": "Sounds",
"Notification sound": "Notification sound",

View file

@ -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,
},
};

20
src/settings/UIFeature.ts Normal file
View file

@ -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",
}

View file

@ -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
}
}