Skip to content

Commit

Permalink
Show all labs even if incompatible, with appropriate tooltip explaini…
Browse files Browse the repository at this point in the history
…ng requirements (#10369)

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
  • Loading branch information
t3chguy and richvdh committed Mar 15, 2023
1 parent 209b652 commit e3930fb
Show file tree
Hide file tree
Showing 16 changed files with 156 additions and 120 deletions.
2 changes: 1 addition & 1 deletion src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
this.closeMenu();
};

private onShareClick = (e: React.MouseEvent): void => {
private onShareClick = (e: ButtonEvent): void => {
e.preventDefault();
Modal.createDialog(ShareDialog, {
target: this.props.mxEvent,
Expand Down
61 changes: 39 additions & 22 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 @@ -32,34 +33,58 @@ 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;
/** true if `SettingsStore.isEnabled` returned false. */
disabled: boolean;
}

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

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

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

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

private getSettingValue(): boolean {
return SettingsStore.getValueAt(
this.props.level,
this.props.name,
this.props.roomId ?? null,
this.props.isExplicit,
);
}

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

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

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 All @@ -86,19 +111,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);

let disabledDescription: JSX.Element | null = null;
if (this.props.disabled && this.props.disabledDescription) {
disabledDescription = <div className="mx_SettingsFlag_microcopy">{this.props.disabledDescription}</div>;
}
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 All @@ -117,18 +134,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}
disabled={disabled}
tooltip={disabled ? SettingsStore.disabledMessage(this.props.name) : undefined}
title={label}
/>
</div>
Expand Down
53 changes: 16 additions & 37 deletions src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,70 +23,49 @@ 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} />);
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 All @@ -50,10 +48,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,

private static KEYBINDINGS_SETTINGS = ["ctrlFForSearch"];

private static PRESENCE_SETTINGS = [
"sendTypingNotifications",
// sendReadReceipts - handled specially due to server needing support
];
private static PRESENCE_SETTINGS = ["sendReadReceipts", "sendTypingNotifications"];

private static COMPOSER_SETTINGS = [
"MessageComposerInput.autoReplaceEmoji",
Expand Down Expand Up @@ -101,7 +96,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 +108,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 +124,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 +186,6 @@ 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}
/>
{this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS)}
</div>

Expand Down
7 changes: 5 additions & 2 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,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 the stable version of MSC3827": "Requires your server to support the 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 @@ -962,7 +963,9 @@
"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",
"Your server doesn't support disabling sending read receipts.": "Your server doesn't support disabling sending read receipts.",
"Sliding Sync mode": "Sliding Sync mode",
"Under active development, cannot be disabled.": "Under active development, cannot be disabled.",
"Element Call video rooms": "Element Call video rooms",
Expand All @@ -979,7 +982,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 @@ -1056,6 +1058,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 Expand Up @@ -1621,7 +1625,6 @@
"Displaying time": "Displaying time",
"Presence": "Presence",
"Share your activity and status with others.": "Share your activity and status with others.",
"Your server doesn't support disabling sending read receipts.": "Your server doesn't support disabling sending read receipts.",
"Composer": "Composer",
"Code blocks": "Code blocks",
"Images, GIFs and videos": "Images, GIFs and videos",
Expand Down
30 changes: 23 additions & 7 deletions src/settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,13 @@ 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"],
undefined,
_td("Requires your server to support the stable version of MSC3827"),
),
},
"feature_msc3531_hide_messages_pending_moderation": {
isFeature: true,
Expand Down Expand Up @@ -373,9 +377,13 @@ 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"],
undefined,
_td("Requires your server to support MSC3030"),
),
},
"RoomList.backgroundImage": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
Expand All @@ -385,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 Expand Up @@ -482,7 +498,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
Loading

0 comments on commit e3930fb

Please sign in to comment.