Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit e3930fb

Browse files
t3chguyrichvdh
andauthored
Show all labs even if incompatible, with appropriate tooltip explaining requirements (#10369)
Co-authored-by: Richard van der Hoff <[email protected]>
1 parent 209b652 commit e3930fb

File tree

16 files changed

+156
-120
lines changed

16 files changed

+156
-120
lines changed

src/components/views/context_menus/MessageContextMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
284284
this.closeMenu();
285285
};
286286

287-
private onShareClick = (e: React.MouseEvent): void => {
287+
private onShareClick = (e: ButtonEvent): void => {
288288
e.preventDefault();
289289
Modal.createDialog(ShareDialog, {
290290
target: this.props.mxEvent,

src/components/views/elements/SettingsFlag.tsx

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { _t } from "../../../languageHandler";
2222
import ToggleSwitch from "./ToggleSwitch";
2323
import StyledCheckbox from "./StyledCheckbox";
2424
import { SettingLevel } from "../../../settings/SettingLevel";
25+
import { defaultWatchManager } from "../../../settings/Settings";
2526

2627
interface IProps {
2728
// The setting must be a boolean
@@ -32,34 +33,58 @@ interface IProps {
3233
isExplicit?: boolean;
3334
// XXX: once design replaces all toggles make this the default
3435
useCheckbox?: boolean;
35-
disabled?: boolean;
36-
disabledDescription?: string;
3736
hideIfCannotSet?: boolean;
3837
onChange?(checked: boolean): void;
3938
}
4039

4140
interface IState {
4241
value: boolean;
42+
/** true if `SettingsStore.isEnabled` returned false. */
43+
disabled: boolean;
4344
}
4445

4546
export default class SettingsFlag extends React.Component<IProps, IState> {
4647
public constructor(props: IProps) {
4748
super(props);
4849

4950
this.state = {
50-
value: SettingsStore.getValueAt(
51-
this.props.level,
52-
this.props.name,
53-
this.props.roomId,
54-
this.props.isExplicit,
55-
),
51+
value: this.getSettingValue(),
52+
disabled: this.isSettingDisabled(),
5653
};
5754
}
5855

56+
public componentDidMount(): void {
57+
defaultWatchManager.watchSetting(this.props.name, this.props.roomId ?? null, this.onSettingChange);
58+
}
59+
60+
public componentWillUnmount(): void {
61+
defaultWatchManager.unwatchSetting(this.onSettingChange);
62+
}
63+
64+
private getSettingValue(): boolean {
65+
return SettingsStore.getValueAt(
66+
this.props.level,
67+
this.props.name,
68+
this.props.roomId ?? null,
69+
this.props.isExplicit,
70+
);
71+
}
72+
73+
private isSettingDisabled(): boolean {
74+
return !SettingsStore.isEnabled(this.props.name);
75+
}
76+
77+
private onSettingChange = (): void => {
78+
this.setState({
79+
value: this.getSettingValue(),
80+
disabled: this.isSettingDisabled(),
81+
});
82+
};
83+
5984
private onChange = async (checked: boolean): Promise<void> => {
6085
await this.save(checked);
6186
this.setState({ value: checked });
62-
if (this.props.onChange) this.props.onChange(checked);
87+
this.props.onChange?.(checked);
6388
};
6489

6590
private checkBoxOnChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
@@ -86,19 +111,11 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
86111
: SettingsStore.getDisplayName(this.props.name, this.props.level)) ?? undefined;
87112
const description = SettingsStore.getDescription(this.props.name);
88113
const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name);
89-
90-
let disabledDescription: JSX.Element | null = null;
91-
if (this.props.disabled && this.props.disabledDescription) {
92-
disabledDescription = <div className="mx_SettingsFlag_microcopy">{this.props.disabledDescription}</div>;
93-
}
114+
const disabled = this.state.disabled || !canChange;
94115

95116
if (this.props.useCheckbox) {
96117
return (
97-
<StyledCheckbox
98-
checked={this.state.value}
99-
onChange={this.checkBoxOnChange}
100-
disabled={this.props.disabled || !canChange}
101-
>
118+
<StyledCheckbox checked={this.state.value} onChange={this.checkBoxOnChange} disabled={disabled}>
102119
{label}
103120
</StyledCheckbox>
104121
);
@@ -117,18 +134,18 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
117134
w: (sub) => (
118135
<span className="mx_SettingsTab_microcopy_warning">{sub}</span>
119136
),
120-
description: description,
137+
description,
121138
},
122139
)
123140
: description}
124141
</div>
125142
)}
126-
{disabledDescription}
127143
</label>
128144
<ToggleSwitch
129145
checked={this.state.value}
130146
onChange={this.onChange}
131-
disabled={this.props.disabled || !canChange}
147+
disabled={disabled}
148+
tooltip={disabled ? SettingsStore.disabledMessage(this.props.name) : undefined}
132149
title={label}
133150
/>
134151
</div>

src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,70 +23,49 @@ import { SettingLevel } from "../../../../../settings/SettingLevel";
2323
import SdkConfig from "../../../../../SdkConfig";
2424
import BetaCard from "../../../beta/BetaCard";
2525
import SettingsFlag from "../../../elements/SettingsFlag";
26-
import { defaultWatchManager, LabGroup, labGroupNames } from "../../../../../settings/Settings";
26+
import { LabGroup, labGroupNames } from "../../../../../settings/Settings";
2727
import { EnhancedMap } from "../../../../../utils/maps";
28-
import { arrayHasDiff } from "../../../../../utils/arrays";
2928

30-
interface State {
31-
labs: string[];
32-
betas: string[];
33-
}
34-
35-
export default class LabsUserSettingsTab extends React.Component<{}, State> {
36-
private readonly features = SettingsStore.getFeatureSettingNames();
29+
export default class LabsUserSettingsTab extends React.Component<{}> {
30+
private readonly labs: string[];
31+
private readonly betas: string[];
3732

3833
public constructor(props: {}) {
3934
super(props);
4035

41-
this.state = {
42-
betas: [],
43-
labs: [],
44-
};
45-
}
46-
47-
public componentDidMount(): void {
48-
this.features.forEach((feature) => {
49-
defaultWatchManager.watchSetting(feature, null, this.onChange);
50-
});
51-
this.onChange();
52-
}
53-
54-
public componentWillUnmount(): void {
55-
defaultWatchManager.unwatchSetting(this.onChange);
56-
}
57-
58-
private onChange = (): void => {
59-
const features = SettingsStore.getFeatureSettingNames().filter((f) => SettingsStore.isEnabled(f));
60-
const [_labs, betas] = features.reduce(
36+
const features = SettingsStore.getFeatureSettingNames();
37+
const [labs, betas] = features.reduce(
6138
(arr, f) => {
6239
arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f);
6340
return arr;
6441
},
6542
[[], []] as [string[], string[]],
6643
);
6744

68-
const labs = SdkConfig.get("show_labs_settings") ? _labs : [];
69-
if (arrayHasDiff(labs, this.state.labs) || arrayHasDiff(betas, this.state.betas)) {
70-
this.setState({ labs, betas });
45+
this.labs = labs;
46+
this.betas = betas;
47+
48+
if (!SdkConfig.get("show_labs_settings")) {
49+
this.labs = [];
7150
}
72-
};
51+
}
7352

7453
public render(): React.ReactNode {
7554
let betaSection: JSX.Element | undefined;
76-
if (this.state.betas.length) {
55+
if (this.betas.length) {
7756
betaSection = (
7857
<div data-testid="labs-beta-section" className="mx_SettingsTab_section">
79-
{this.state.betas.map((f) => (
58+
{this.betas.map((f) => (
8059
<BetaCard key={f} featureId={f} />
8160
))}
8261
</div>
8362
);
8463
}
8564

8665
let labsSections: JSX.Element | undefined;
87-
if (this.state.labs.length) {
66+
if (this.labs.length) {
8867
const groups = new EnhancedMap<LabGroup, JSX.Element[]>();
89-
this.state.labs.forEach((f) => {
68+
this.labs.forEach((f) => {
9069
groups
9170
.getOrCreate(SettingsStore.getLabGroup(f), [])
9271
.push(<SettingsFlag level={SettingLevel.DEVICE} name={f} key={f} />);

src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ import { UserTab } from "../../../dialogs/UserTab";
2929
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
3030
import { Action } from "../../../../../dispatcher/actions";
3131
import SdkConfig from "../../../../../SdkConfig";
32-
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
3332
import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingPage";
3433

3534
interface IProps {
3635
closeSettingsFn(success: boolean): void;
3736
}
3837

3938
interface IState {
40-
disablingReadReceiptsSupported: boolean;
4139
autocompleteDelay: string;
4240
readMarkerInViewThresholdMs: string;
4341
readMarkerOutOfViewThresholdMs: string;
@@ -50,10 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
5048

5149
private static KEYBINDINGS_SETTINGS = ["ctrlFForSearch"];
5250

53-
private static PRESENCE_SETTINGS = [
54-
"sendTypingNotifications",
55-
// sendReadReceipts - handled specially due to server needing support
56-
];
51+
private static PRESENCE_SETTINGS = ["sendReadReceipts", "sendTypingNotifications"];
5752

5853
private static COMPOSER_SETTINGS = [
5954
"MessageComposerInput.autoReplaceEmoji",
@@ -101,7 +96,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
10196
super(props);
10297

10398
this.state = {
104-
disablingReadReceiptsSupported: false,
10599
autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, "autocompleteDelay").toString(10),
106100
readMarkerInViewThresholdMs: SettingsStore.getValueAt(
107101
SettingLevel.DEVICE,
@@ -114,16 +108,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
114108
};
115109
}
116110

117-
public async componentDidMount(): Promise<void> {
118-
const cli = MatrixClientPeg.get();
119-
120-
this.setState({
121-
disablingReadReceiptsSupported:
122-
(await cli.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) ||
123-
(await cli.isVersionSupported("v1.4")),
124-
});
125-
}
126-
127111
private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
128112
this.setState({ autocompleteDelay: e.target.value });
129113
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
@@ -140,10 +124,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
140124
};
141125

142126
private renderGroup(settingIds: string[], level = SettingLevel.ACCOUNT): React.ReactNodeArray {
143-
return settingIds.map((i) => {
144-
const disabled = !SettingsStore.isEnabled(i);
145-
return <SettingsFlag key={i} name={i} level={level} disabled={disabled} />;
146-
});
127+
return settingIds.map((i) => <SettingsFlag key={i} name={i} level={level} />);
147128
}
148129

149130
private onKeyboardShortcutsClicked = (): void => {
@@ -205,14 +186,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
205186
<span className="mx_SettingsTab_subsectionText">
206187
{_t("Share your activity and status with others.")}
207188
</span>
208-
<SettingsFlag
209-
disabled={
210-
!this.state.disablingReadReceiptsSupported && SettingsStore.getValue("sendReadReceipts") // Make sure the feature can always be enabled
211-
}
212-
disabledDescription={_t("Your server doesn't support disabling sending read receipts.")}
213-
name="sendReadReceipts"
214-
level={SettingLevel.ACCOUNT}
215-
/>
216189
{this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS)}
217190
</div>
218191

src/i18n/strings/en_EN.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -939,6 +939,7 @@
939939
"Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.",
940940
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.",
941941
"Explore public spaces in the new search dialog": "Explore public spaces in the new search dialog",
942+
"Requires your server to support the stable version of MSC3827": "Requires your server to support the stable version of MSC3827",
942943
"Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.",
943944
"Report to moderators": "Report to moderators",
944945
"In rooms that support moderation, the “Report” button will let you report abuse to room moderators.": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
@@ -962,7 +963,9 @@
962963
"Polls history": "Polls history",
963964
"View a list of polls in a room. (Under active development)": "View a list of polls in a room. (Under active development)",
964965
"Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)",
966+
"Requires your server to support MSC3030": "Requires your server to support MSC3030",
965967
"Send read receipts": "Send read receipts",
968+
"Your server doesn't support disabling sending read receipts.": "Your server doesn't support disabling sending read receipts.",
966969
"Sliding Sync mode": "Sliding Sync mode",
967970
"Under active development, cannot be disabled.": "Under active development, cannot be disabled.",
968971
"Element Call video rooms": "Element Call video rooms",
@@ -979,7 +982,6 @@
979982
"Have greater visibility and control over all your sessions.": "Have greater visibility and control over all your sessions.",
980983
"Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.": "Our new sessions manager provides better visibility of all your sessions, and greater control over them including the ability to remotely toggle push notifications.",
981984
"Rust cryptography implementation": "Rust cryptography implementation",
982-
"Under active development. Can currently only be enabled via config.json": "Under active development. Can currently only be enabled via config.json",
983985
"Font size": "Font size",
984986
"Use custom size": "Use custom size",
985987
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
@@ -1056,6 +1058,8 @@
10561058
"Always show the window menu bar": "Always show the window menu bar",
10571059
"Show tray icon and minimise window to it on close": "Show tray icon and minimise window to it on close",
10581060
"Enable hardware acceleration": "Enable hardware acceleration",
1061+
"Can currently only be enabled via config.json": "Can currently only be enabled via config.json",
1062+
"Log out and back in to disable": "Log out and back in to disable",
10591063
"Collecting app version information": "Collecting app version information",
10601064
"Collecting logs": "Collecting logs",
10611065
"Uploading logs": "Uploading logs",
@@ -1621,7 +1625,6 @@
16211625
"Displaying time": "Displaying time",
16221626
"Presence": "Presence",
16231627
"Share your activity and status with others.": "Share your activity and status with others.",
1624-
"Your server doesn't support disabling sending read receipts.": "Your server doesn't support disabling sending read receipts.",
16251628
"Composer": "Composer",
16261629
"Code blocks": "Code blocks",
16271630
"Images, GIFs and videos": "Images, GIFs and videos",

src/settings/Settings.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
227227
displayName: _td("Explore public spaces in the new search dialog"),
228228
supportedLevels: LEVELS_FEATURE,
229229
default: false,
230-
controller: new ServerSupportUnstableFeatureController("feature_exploring_public_spaces", defaultWatchManager, [
231-
"org.matrix.msc3827.stable",
232-
]),
230+
controller: new ServerSupportUnstableFeatureController(
231+
"feature_exploring_public_spaces",
232+
defaultWatchManager,
233+
["org.matrix.msc3827.stable"],
234+
undefined,
235+
_td("Requires your server to support the stable version of MSC3827"),
236+
),
233237
},
234238
"feature_msc3531_hide_messages_pending_moderation": {
235239
isFeature: true,
@@ -373,9 +377,13 @@ export const SETTINGS: { [setting: string]: ISetting } = {
373377
displayName: _td("Jump to date (adds /jumptodate and jump to date headers)"),
374378
supportedLevels: LEVELS_FEATURE,
375379
default: false,
376-
controller: new ServerSupportUnstableFeatureController("feature_jump_to_date", defaultWatchManager, [
377-
"org.matrix.msc3030",
378-
]),
380+
controller: new ServerSupportUnstableFeatureController(
381+
"feature_jump_to_date",
382+
defaultWatchManager,
383+
["org.matrix.msc3030"],
384+
undefined,
385+
_td("Requires your server to support MSC3030"),
386+
),
379387
},
380388
"RoomList.backgroundImage": {
381389
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
@@ -385,6 +393,14 @@ export const SETTINGS: { [setting: string]: ISetting } = {
385393
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
386394
displayName: _td("Send read receipts"),
387395
default: true,
396+
controller: new ServerSupportUnstableFeatureController(
397+
"sendReadReceipts",
398+
defaultWatchManager,
399+
["org.matrix.msc2285.stable"],
400+
"v1.4",
401+
_td("Your server doesn't support disabling sending read receipts."),
402+
true,
403+
),
388404
},
389405
"feature_sliding_sync": {
390406
isFeature: true,
@@ -482,7 +498,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
482498
labsGroup: LabGroup.Developer,
483499
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
484500
displayName: _td("Rust cryptography implementation"),
485-
description: _td("Under active development. Can currently only be enabled via config.json"),
501+
description: _td("Under active development."),
486502
// shouldWarn: true,
487503
default: false,
488504
controller: new RustCryptoSdkController(),

0 commit comments

Comments
 (0)