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

Show all labs even if incompatible, with appropriate tooltip explaining requirements #10369

Merged
merged 5 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/components/views/elements/SettingsFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,13 @@ interface IProps {
isExplicit?: boolean;
// XXX: once design replaces all toggles make this the default
useCheckbox?: boolean;
disabled?: boolean;
disabledDescription?: string;
hideIfCannotSet?: boolean;
onChange?(checked: boolean): void;
}

interface IState {
value: boolean;
disabled: boolean;
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
}

export default class SettingsFlag extends React.Component<IProps, IState> {
Expand All @@ -49,6 +48,7 @@ export default class SettingsFlag extends React.Component<IProps, IState> {

this.state = {
value: this.settingValue,
disabled: this.settingDisabled,
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand All @@ -64,8 +64,15 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
return SettingsStore.getValueAt(this.props.level, this.props.name, this.props.roomId, this.props.isExplicit);
}

private get settingDisabled(): boolean {
return !SettingsStore.isEnabled(this.props.name);
}

private onSettingChange = (): void => {
this.setState({ value: this.settingValue });
this.setState({
value: this.settingValue,
disabled: this.settingDisabled,
});
};

private onChange = async (checked: boolean): Promise<void> => {
Expand Down Expand Up @@ -98,14 +105,11 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
: SettingsStore.getDisplayName(this.props.name, this.props.level)) ?? undefined;
const description = SettingsStore.getDescription(this.props.name);
const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name);
const disabled = this.state.disabled || !canChange;

if (this.props.useCheckbox) {
return (
<StyledCheckbox
checked={this.state.value}
onChange={this.checkBoxOnChange}
disabled={this.props.disabled || !canChange}
>
<StyledCheckbox checked={this.state.value} onChange={this.checkBoxOnChange} disabled={disabled}>
{label}
</StyledCheckbox>
);
Expand Down Expand Up @@ -134,8 +138,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
<ToggleSwitch
checked={this.state.value}
onChange={this.onChange}
disabled={this.props.disabled || !canChange}
tooltip={this.props.disabled ? this.props.disabledDescription : undefined}
disabled={disabled}
tooltip={disabled ? SettingsStore.disabledMessage(this.props.name) : undefined}
title={label}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,7 @@ export default class LabsUserSettingsTab extends React.Component<{}> {
this.labs.forEach((f) => {
groups
.getOrCreate(SettingsStore.getLabGroup(f), [])
.push(
<SettingsFlag
level={SettingLevel.DEVICE}
name={f}
key={f}
disabled={!SettingsStore.isEnabled(f)}
disabledDescription={SettingsStore.disabledMessage(f)}
/>,
);
.push(<SettingsFlag level={SettingLevel.DEVICE} name={f} key={f} />);
});

groups
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,13 @@ import { UserTab } from "../../../dialogs/UserTab";
import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload";
import { Action } from "../../../../../dispatcher/actions";
import SdkConfig from "../../../../../SdkConfig";
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingPage";

interface IProps {
closeSettingsFn(success: boolean): void;
}

interface IState {
disablingReadReceiptsSupported: boolean;
autocompleteDelay: string;
readMarkerInViewThresholdMs: string;
readMarkerOutOfViewThresholdMs: string;
Expand Down Expand Up @@ -101,7 +99,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
super(props);

this.state = {
disablingReadReceiptsSupported: false,
autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, "autocompleteDelay").toString(10),
readMarkerInViewThresholdMs: SettingsStore.getValueAt(
SettingLevel.DEVICE,
Expand All @@ -114,16 +111,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
};
}

public async componentDidMount(): Promise<void> {
const cli = MatrixClientPeg.get();

this.setState({
disablingReadReceiptsSupported:
(await cli.doesServerSupportUnstableFeature("org.matrix.msc2285.stable")) ||
(await cli.isVersionSupported("v1.4")),
});
}

private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
this.setState({ autocompleteDelay: e.target.value });
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
Expand All @@ -140,10 +127,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
};

private renderGroup(settingIds: string[], level = SettingLevel.ACCOUNT): React.ReactNodeArray {
return settingIds.map((i) => {
const disabled = !SettingsStore.isEnabled(i);
return <SettingsFlag key={i} name={i} level={level} disabled={disabled} />;
});
return settingIds.map((i) => <SettingsFlag key={i} name={i} level={level} />);
}

private onKeyboardShortcutsClicked = (): void => {
Expand Down Expand Up @@ -205,14 +189,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
<span className="mx_SettingsTab_subsectionText">
{_t("Share your activity and status with others.")}
</span>
<SettingsFlag
disabled={
!this.state.disablingReadReceiptsSupported && SettingsStore.getValue("sendReadReceipts") // Make sure the feature can always be enabled
}
disabledDescription={_t("Your server doesn't support disabling sending read receipts.")}
name="sendReadReceipts"
level={SettingLevel.ACCOUNT}
/>
<SettingsFlag name="sendReadReceipts" level={SettingLevel.ACCOUNT} />
{this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS)}
</div>

Expand Down
10 changes: 10 additions & 0 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
"feature_exploring_public_spaces",
defaultWatchManager,
["org.matrix.msc3827.stable"],
undefined,
_td("Requires your server to support stable version of MSC3827"),
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
),
},
Expand Down Expand Up @@ -380,6 +381,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
"feature_jump_to_date",
defaultWatchManager,
["org.matrix.msc3030"],
undefined,
_td("Requires your server to support MSC3030"),
),
},
Expand All @@ -391,6 +393,14 @@ export const SETTINGS: { [setting: string]: ISetting } = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td("Send read receipts"),
default: true,
controller: new ServerSupportUnstableFeatureController(
"sendReadReceipts",
defaultWatchManager,
["org.matrix.msc2285.stable"],
"v1.4",
_td("Your server doesn't support disabling sending read receipts."),
true,
),
},
"feature_sliding_sync": {
isFeature: true,
Expand Down
4 changes: 3 additions & 1 deletion src/settings/SettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,9 @@ export default class SettingsStore {

/**
* Determines if a setting is enabled.
* If a setting is disabled then it should be hidden from the user.
* If a setting is disabled then it should be hidden from the user to de-clutter the user interface,
* this rule is intentionally ignored for labs flags to unveil what features are available with
* the right server support.
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
* @param {string} settingName The setting to look up.
* @return {boolean} True if the setting is enabled.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
private readonly settingName: string,
private readonly watchers: WatchManager,
private readonly unstableFeatures: string[],
private readonly stableVersion?: string,
private readonly disabledMessage?: string,
private readonly forcedValue: any = false,
) {
Expand All @@ -54,10 +55,16 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
protected async initMatrixClient(oldClient: MatrixClient, newClient: MatrixClient): Promise<void> {
this.disabled = true;
let supported = true;

if (this.stableVersion && (await this.client.isVersionSupported(this.stableVersion))) {
this.disabled = false;
return;
}
for (const feature of this.unstableFeatures) {
supported = await this.client.doesServerSupportUnstableFeature(feature);
if (!supported) break;
}

this.disabled = !supported;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

import React from "react";
import { render } from "@testing-library/react";
import { render, waitFor } from "@testing-library/react";
import { defer } from "matrix-js-sdk/src/utils";

import LabsUserSettingsTab from "../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
Expand All @@ -28,7 +28,7 @@ import {
import SdkConfig from "../../../../../../src/SdkConfig";
import MatrixClientBackedController from "../../../../../../src/settings/controllers/MatrixClientBackedController";

describe("<SecurityUserSettingsTab />", () => {
describe("<LabsUserSettingsTab />", () => {
const sdkConfigSpy = jest.spyOn(SdkConfig, "get");

const defaultProps = {
Expand Down Expand Up @@ -70,10 +70,10 @@ describe("<SecurityUserSettingsTab />", () => {
const { container } = render(getComponent());

const labsSections = container.getElementsByClassName("mx_SettingsTab_section");
expect(labsSections.length).toEqual(11);
expect(labsSections).toHaveLength(12);
});

it("renders a labs flag which requires unstable support once support is confirmed", async () => {
it("allow setting a labs flag which requires unstable support once support is confirmed", async () => {
// enable labs
sdkConfigSpy.mockImplementation((configName) => configName === "show_labs_settings");

Expand All @@ -83,10 +83,20 @@ describe("<SecurityUserSettingsTab />", () => {
});
MatrixClientBackedController.matrixClient = cli;

const { queryByText, findByText } = render(getComponent());
const { queryByText } = render(getComponent());

expect(queryByText("Explore public spaces in the new search dialog")).toBeFalsy();
expect(
queryByText("Explore public spaces in the new search dialog")
.closest(".mx_SettingsFlag")
.querySelector(".mx_AccessibleButton"),
).toHaveAttribute("aria-disabled", "true");
deferred.resolve(true);
await expect(findByText("Explore public spaces in the new search dialog")).resolves.toBeDefined();
await waitFor(() => {
expect(
queryByText("Explore public spaces in the new search dialog")
.closest(".mx_SettingsFlag")
.querySelector(".mx_AccessibleButton"),
).toHaveAttribute("aria-disabled", "false");
});
});
});
Loading