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

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
This commit is contained in:
Michael Telatynski 2023-03-15 08:37:41 +00:00 committed by GitHub
parent 209b65243a
commit e3930fb8b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 156 additions and 120 deletions

View file

@ -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,

View file

@ -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
@ -32,14 +33,14 @@ 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> {
@ -47,19 +48,43 @@ 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.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 => {
@ -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>
);
@ -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>

View file

@ -23,41 +23,18 @@ 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;
@ -65,18 +42,20 @@ export default class LabsUserSettingsTab extends React.Component<{}, State> {
[[], []] 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>
@ -84,9 +63,9 @@ export default class LabsUserSettingsTab extends React.Component<{}, State> {
}
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} />);

View file

@ -29,7 +29,6 @@ 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 {
@ -37,7 +36,6 @@ interface IProps {
}
interface IState {
disablingReadReceiptsSupported: boolean;
autocompleteDelay: string;
readMarkerInViewThresholdMs: string;
readMarkerOutOfViewThresholdMs: string;
@ -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",
@ -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,
@ -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);
@ -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 => {
@ -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>