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:
parent
ab91ce4b2d
commit
f4f30a3274
7 changed files with 108 additions and 6 deletions
|
@ -120,6 +120,18 @@ Call `SettingsStore.getValue()` as you would for any other setting.
|
||||||
|
|
||||||
Call `SettingsStore.setValue("feature_name", null, SettingLevel.DEVICE, true)`.
|
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
|
## 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
|
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
|
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'.
|
setting themselves as there's nothing to really 'watch'.
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import * as sdk from "../../../../..";
|
||||||
import AccessibleButton from "../../../elements/AccessibleButton";
|
import AccessibleButton from "../../../elements/AccessibleButton";
|
||||||
import dis from "../../../../../dispatcher/dispatcher";
|
import dis from "../../../../../dispatcher/dispatcher";
|
||||||
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
|
||||||
|
import SettingsStore from "../../../../../settings/SettingsStore";
|
||||||
|
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||||
|
|
||||||
export default class GeneralRoomSettingsTab extends React.Component {
|
export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -61,6 +63,16 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
|
const canChangeGroups = room.currentState.mayClientSendStateEvent("m.room.related_groups", client);
|
||||||
const groupsEvent = room.currentState.getStateEvents("m.room.related_groups", "");
|
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 (
|
return (
|
||||||
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
<div className="mx_SettingsTab mx_GeneralRoomSettingsTab">
|
||||||
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
<div className="mx_SettingsTab_heading">{_t("General")}</div>
|
||||||
|
@ -82,10 +94,7 @@ export default class GeneralRoomSettingsTab extends React.Component {
|
||||||
relatedGroupsEvent={groupsEvent} />
|
relatedGroupsEvent={groupsEvent} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("URL Previews")}</span>
|
{urlPreviewSettings}
|
||||||
<div className='mx_SettingsTab_section'>
|
|
||||||
<UrlPreviewSettings room={room} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
|
<span className='mx_SettingsTab_subheading'>{_t("Leave room")}</span>
|
||||||
<div className='mx_SettingsTab_section'>
|
<div className='mx_SettingsTab_section'>
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Field from "../../../elements/Field";
|
||||||
import * as sdk from "../../../../..";
|
import * as sdk from "../../../../..";
|
||||||
import PlatformPeg from "../../../../../PlatformPeg";
|
import PlatformPeg from "../../../../../PlatformPeg";
|
||||||
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
import {SettingLevel} from "../../../../../settings/SettingLevel";
|
||||||
|
import {UIFeature} from "../../../../../settings/UIFeature";
|
||||||
|
|
||||||
export default class PreferencesUserSettingsTab extends React.Component {
|
export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
static ROOM_LIST_SETTINGS = [
|
static ROOM_LIST_SETTINGS = [
|
||||||
|
@ -138,6 +139,10 @@ export default class PreferencesUserSettingsTab extends React.Component {
|
||||||
};
|
};
|
||||||
|
|
||||||
_renderGroup(settingIds) {
|
_renderGroup(settingIds) {
|
||||||
|
if (!SettingsStore.getValue(UIFeature.URLPreviews)) {
|
||||||
|
settingIds = settingIds.filter(i => i !== 'urlPreviewsEnabled');
|
||||||
|
}
|
||||||
|
|
||||||
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
|
||||||
return settingIds.map(i => <SettingsFlag key={i} name={i} level={SettingLevel.ACCOUNT} />);
|
return settingIds.map(i => <SettingsFlag key={i} name={i} level={SettingLevel.ACCOUNT} />);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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 isn’t bridging messages to any platforms. <a>Learn more.</a>": "This room isn’t bridging messages to any platforms. <a>Learn more.</a>",
|
"This room isn’t bridging messages to any platforms. <a>Learn more.</a>": "This room isn’t bridging messages to any platforms. <a>Learn more.</a>",
|
||||||
"Bridges": "Bridges",
|
"Bridges": "Bridges",
|
||||||
"Room Addresses": "Room Addresses",
|
|
||||||
"URL Previews": "URL Previews",
|
"URL Previews": "URL Previews",
|
||||||
|
"Room Addresses": "Room Addresses",
|
||||||
"Uploaded sound": "Uploaded sound",
|
"Uploaded sound": "Uploaded sound",
|
||||||
"Sounds": "Sounds",
|
"Sounds": "Sounds",
|
||||||
"Notification sound": "Notification sound",
|
"Notification sound": "Notification sound",
|
||||||
|
|
|
@ -32,6 +32,8 @@ import UseSystemFontController from './controllers/UseSystemFontController';
|
||||||
import { SettingLevel } from "./SettingLevel";
|
import { SettingLevel } from "./SettingLevel";
|
||||||
import SettingController from "./controllers/SettingController";
|
import SettingController from "./controllers/SettingController";
|
||||||
import { RightPanelPhases } from "../stores/RightPanelStorePhases";
|
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
|
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
|
||||||
const LEVELS_ROOM_SETTINGS = [
|
const LEVELS_ROOM_SETTINGS = [
|
||||||
|
@ -69,6 +71,10 @@ const LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG = [
|
||||||
SettingLevel.DEVICE,
|
SettingLevel.DEVICE,
|
||||||
SettingLevel.CONFIG,
|
SettingLevel.CONFIG,
|
||||||
];
|
];
|
||||||
|
const LEVELS_UI_FEATURE = [
|
||||||
|
SettingLevel.CONFIG,
|
||||||
|
// in future we might have a .well-known level or something
|
||||||
|
];
|
||||||
|
|
||||||
export interface ISetting {
|
export interface ISetting {
|
||||||
// Must be set to true for features. Default is 'false'.
|
// 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"),
|
"room": _td("Enable URL previews by default for participants in this room"),
|
||||||
},
|
},
|
||||||
default: true,
|
default: true,
|
||||||
|
controller: new UIFeatureController(UIFeature.URLPreviews),
|
||||||
},
|
},
|
||||||
"urlPreviewsEnabled_e2ee": {
|
"urlPreviewsEnabled_e2ee": {
|
||||||
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
|
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)"),
|
"room-account": _td("Enable URL previews for this room (only affects you)"),
|
||||||
},
|
},
|
||||||
default: false,
|
default: false,
|
||||||
|
controller: new UIFeatureController(UIFeature.URLPreviews),
|
||||||
},
|
},
|
||||||
"roomColor": {
|
"roomColor": {
|
||||||
supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM,
|
supportedLevels: LEVELS_ROOM_SETTINGS_WITH_ROOM,
|
||||||
|
@ -611,4 +619,8 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
supportedLevels: LEVELS_ROOM_OR_ACCOUNT,
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
|
[UIFeature.URLPreviews]: {
|
||||||
|
supportedLevels: LEVELS_UI_FEATURE,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
20
src/settings/UIFeature.ts
Normal file
20
src/settings/UIFeature.ts
Normal 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",
|
||||||
|
}
|
45
src/settings/controllers/UIFeatureController.ts
Normal file
45
src/settings/controllers/UIFeatureController.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue