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 all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,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
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
className="mx_RoomTile_contextMenu"
compact
rightAligned
{...contextMenuBelow(button.current.getBoundingClientRect())}
{...contextMenuBelow(button.current!.getBoundingClientRect())}
>
<IconizedContextMenuOptionList>
{isMainSplitTimelineShown && (
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;
t3chguy marked this conversation as resolved.
Show resolved Hide resolved
}

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 @@ -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 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 @@ -961,7 +962,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 @@ -978,7 +981,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 +1057,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 @@ -1620,7 +1624,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
Loading