Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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
35 changes: 21 additions & 14 deletions src/components/views/elements/SettingsFlag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { _t } from "../../../languageHandler";
import ToggleSwitch from "./ToggleSwitch";
import StyledCheckbox from "./StyledCheckbox";
import { SettingLevel } from "../../../settings/SettingLevel";
import { defaultWatchManager } from "../../../settings/Settings";

interface IProps {
// The setting must be a boolean
Expand All @@ -47,19 +48,30 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
super(props);

this.state = {
value: SettingsStore.getValueAt(
this.props.level,
this.props.name,
this.props.roomId,
this.props.isExplicit,
),
value: this.settingValue,
};
}

public componentDidMount(): void {
defaultWatchManager.watchSetting(this.props.name, this.props.roomId, this.onSettingChange);
}

public componentWillUnmount(): void {
defaultWatchManager.unwatchSetting(this.onSettingChange);
}

private get settingValue(): boolean {
return SettingsStore.getValueAt(this.props.level, this.props.name, this.props.roomId, this.props.isExplicit);
}

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

private onChange = async (checked: boolean): Promise<void> => {
await this.save(checked);
this.setState({ value: checked });
if (this.props.onChange) this.props.onChange(checked);
this.props.onChange?.(checked);
};

private checkBoxOnChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
Expand Down Expand Up @@ -87,11 +99,6 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
const description = SettingsStore.getDescription(this.props.name);
const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name);

let disabledDescription: JSX.Element | null = null;
if (this.props.disabled && this.props.disabledDescription) {
disabledDescription = <div className="mx_SettingsFlag_microcopy">{this.props.disabledDescription}</div>;
}

if (this.props.useCheckbox) {
return (
<StyledCheckbox
Expand All @@ -117,18 +124,18 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
w: (sub) => (
<span className="mx_SettingsTab_microcopy_warning">{sub}</span>
),
description: description,
description,
},
)
: description}
</div>
)}
{disabledDescription}
</label>
<ToggleSwitch
checked={this.state.value}
onChange={this.onChange}
disabled={this.props.disabled || !canChange}
tooltip={this.props.disabled ? this.props.disabledDescription : undefined}
title={label}
/>
</div>
Expand Down
63 changes: 25 additions & 38 deletions src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,73 +23,60 @@ import { SettingLevel } from "../../../../../settings/SettingLevel";
import SdkConfig from "../../../../../SdkConfig";
import BetaCard from "../../../beta/BetaCard";
import SettingsFlag from "../../../elements/SettingsFlag";
import { defaultWatchManager, LabGroup, labGroupNames } from "../../../../../settings/Settings";
import { LabGroup, labGroupNames } from "../../../../../settings/Settings";
import { EnhancedMap } from "../../../../../utils/maps";
import { arrayHasDiff } from "../../../../../utils/arrays";

interface State {
labs: string[];
betas: string[];
}

export default class LabsUserSettingsTab extends React.Component<{}, State> {
private readonly features = SettingsStore.getFeatureSettingNames();
export default class LabsUserSettingsTab extends React.Component<{}> {
private readonly labs: string[];
private readonly betas: string[];

public constructor(props: {}) {
super(props);

this.state = {
betas: [],
labs: [],
};
}

public componentDidMount(): void {
this.features.forEach((feature) => {
defaultWatchManager.watchSetting(feature, null, this.onChange);
});
this.onChange();
}

public componentWillUnmount(): void {
defaultWatchManager.unwatchSetting(this.onChange);
}

private onChange = (): void => {
const features = SettingsStore.getFeatureSettingNames().filter((f) => SettingsStore.isEnabled(f));
const [_labs, betas] = features.reduce(
const features = SettingsStore.getFeatureSettingNames();
const [labs, betas] = features.reduce(
(arr, f) => {
arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f);
return arr;
},
[[], []] as [string[], string[]],
);

const labs = SdkConfig.get("show_labs_settings") ? _labs : [];
if (arrayHasDiff(labs, this.state.labs) || arrayHasDiff(betas, this.state.betas)) {
this.setState({ labs, betas });
this.labs = labs;
this.betas = betas;

if (!SdkConfig.get("show_labs_settings")) {
this.labs = [];
}
};
}

public render(): React.ReactNode {
let betaSection: JSX.Element | undefined;
if (this.state.betas.length) {
if (this.betas.length) {
betaSection = (
<div data-testid="labs-beta-section" className="mx_SettingsTab_section">
{this.state.betas.map((f) => (
{this.betas.map((f) => (
<BetaCard key={f} featureId={f} />
))}
</div>
);
}

let labsSections: JSX.Element | undefined;
if (this.state.labs.length) {
if (this.labs.length) {
const groups = new EnhancedMap<LabGroup, JSX.Element[]>();
this.state.labs.forEach((f) => {
this.labs.forEach((f) => {
groups
.getOrCreate(SettingsStore.getLabGroup(f), [])
.push(<SettingsFlag level={SettingLevel.DEVICE} name={f} key={f} />);
.push(
<SettingsFlag
level={SettingLevel.DEVICE}
name={f}
key={f}
disabled={!SettingsStore.isEnabled(f)}
disabledDescription={SettingsStore.disabledMessage(f)}
/>,
);
});

groups
Expand Down
5 changes: 4 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@
"Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.",
"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.",
"Explore public spaces in the new search dialog": "Explore public spaces in the new search dialog",
"Requires your server to support stable version of MSC3827": "Requires your server to support stable version of MSC3827",
"Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.",
"Report to moderators": "Report to moderators",
"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.",
Expand All @@ -961,6 +962,7 @@
"Polls history": "Polls history",
"View a list of polls in a room. (Under active development)": "View a list of polls in a room. (Under active development)",
"Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)",
"Requires your server to support MSC3030": "Requires your server to support MSC3030",
"Send read receipts": "Send read receipts",
"Sliding Sync mode": "Sliding Sync mode",
"Under active development, cannot be disabled.": "Under active development, cannot be disabled.",
Expand All @@ -978,7 +980,6 @@
"Have greater visibility and control over all your sessions.": "Have greater visibility and control over all your sessions.",
"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.",
"Rust cryptography implementation": "Rust cryptography implementation",
"Under active development. Can currently only be enabled via config.json": "Under active development. Can currently only be enabled via config.json",
"Font size": "Font size",
"Use custom size": "Use custom size",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
Expand Down Expand Up @@ -1055,6 +1056,8 @@
"Always show the window menu bar": "Always show the window menu bar",
"Show tray icon and minimise window to it on close": "Show tray icon and minimise window to it on close",
"Enable hardware acceleration": "Enable hardware acceleration",
"Can currently only be enabled via config.json": "Can currently only be enabled via config.json",
"Log out and back in to disable": "Log out and back in to disable",
"Collecting app version information": "Collecting app version information",
"Collecting logs": "Collecting logs",
"Uploading logs": "Uploading logs",
Expand Down
20 changes: 13 additions & 7 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,12 @@ export const SETTINGS: { [setting: string]: ISetting } = {
displayName: _td("Explore public spaces in the new search dialog"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ServerSupportUnstableFeatureController("feature_exploring_public_spaces", defaultWatchManager, [
"org.matrix.msc3827.stable",
]),
controller: new ServerSupportUnstableFeatureController(
"feature_exploring_public_spaces",
defaultWatchManager,
["org.matrix.msc3827.stable"],
_td("Requires your server to support stable version of MSC3827"),
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
),
},
"feature_msc3531_hide_messages_pending_moderation": {
isFeature: true,
Expand Down Expand Up @@ -373,9 +376,12 @@ export const SETTINGS: { [setting: string]: ISetting } = {
displayName: _td("Jump to date (adds /jumptodate and jump to date headers)"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ServerSupportUnstableFeatureController("feature_jump_to_date", defaultWatchManager, [
"org.matrix.msc3030",
]),
controller: new ServerSupportUnstableFeatureController(
"feature_jump_to_date",
defaultWatchManager,
["org.matrix.msc3030"],
_td("Requires your server to support MSC3030"),
),
},
"RoomList.backgroundImage": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
Expand Down Expand Up @@ -482,7 +488,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
labsGroup: LabGroup.Developer,
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
displayName: _td("Rust cryptography implementation"),
description: _td("Under active development. Can currently only be enabled via config.json"),
description: _td("Under active development."),
// shouldWarn: true,
default: false,
controller: new RustCryptoSdkController(),
Expand Down
11 changes: 11 additions & 0 deletions src/settings/SettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,17 @@ export default class SettingsStore {
return !SETTINGS[settingName].controller?.settingDisabled ?? true;
}

/**
* Retrieves the reason a setting is disabled if one is assigned.
* If a setting is not disabled this will return undefined.
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
* @param {string} settingName The setting to look up.
* @return {string} The reason the setting is disabled.
*/
public static disabledMessage(settingName: string): string | undefined {
const disabled = SETTINGS[settingName].controller?.settingDisabled;
return typeof disabled === "string" ? disabled : undefined;
}

/**
* Gets the value of a setting. The room ID is optional if the setting is not to
* be applied to any particular room, otherwise it should be supplied.
Expand Down
5 changes: 3 additions & 2 deletions src/settings/controllers/RustCryptoSdkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { _t } from "../../languageHandler";
import SettingController from "./SettingController";

export default class RustCryptoSdkController extends SettingController {
public get settingDisabled(): boolean {
public get settingDisabled(): boolean | string {
// Currently this can only be changed via config.json. In future, we'll allow the user to *enable* this setting
// via labs, which will migrate their existing device to the rust-sdk implementation.
return true;
return _t("Can currently only be enabled via config.json");
}
}
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 disabledMessage?: string,
private readonly forcedValue: any = false,
) {
super();
Expand Down Expand Up @@ -72,7 +73,10 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
return null; // no override
}

public get settingDisabled(): boolean {
return this.disabled;
public get settingDisabled(): boolean | string {
if (this.disabled) {
return this.disabledMessage ?? true;
}
return false;
}
}
3 changes: 2 additions & 1 deletion src/settings/controllers/SettingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export default abstract class SettingController {

/**
* Gets whether the setting has been disabled due to this controller.
* Can also return a string with the reason the setting is disabled.
*/
public get settingDisabled(): boolean {
public get settingDisabled(): boolean | string {
return false;
}
}
9 changes: 7 additions & 2 deletions src/settings/controllers/SlidingSyncController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SettingLevel } from "../SettingLevel";
import { SlidingSyncOptionsDialog } from "../../components/views/dialogs/SlidingSyncOptionsDialog";
import Modal from "../../Modal";
import SettingsStore from "../SettingsStore";
import { _t } from "../../languageHandler";

export default class SlidingSyncController extends SettingController {
public async beforeChange(level: SettingLevel, roomId: string, newValue: any): Promise<boolean> {
Expand All @@ -32,8 +33,12 @@ export default class SlidingSyncController extends SettingController {
PlatformPeg.get()?.reload();
}

public get settingDisabled(): boolean {
public get settingDisabled(): boolean | string {
// Cannot be disabled once enabled, user has been warned and must log out and back in.
return SettingsStore.getValue("feature_sliding_sync");
if (SettingsStore.getValue("feature_sliding_sync")) {
return _t("Log out and back in to disable");
}

return false;
}
}