/* Copyright 2024 New Vector Ltd. Copyright 2019 The Matrix.org Foundation C.I.C. Copyright 2017 Travis Ralston SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ import React, { type ChangeEvent } from "react"; import { secureRandomString } from "matrix-js-sdk/src/randomstring"; import { SettingsToggleInput } from "@vector-im/compound-web"; import { logger } from "matrix-js-sdk/src/logger"; import SettingsStore from "../../../settings/SettingsStore"; import { _t } from "../../../languageHandler"; import { type SettingLevel } from "../../../settings/SettingLevel"; import { type NullableBooleanSettingKey, defaultWatchManager } from "../../../settings/Settings"; interface IProps { // The setting must be a boolean name: NullableBooleanSettingKey; level: SettingLevel; roomId?: string; // for per-room settings label?: string; isExplicit?: boolean; hideIfCannotSet?: boolean; requires?: NullableBooleanSettingKey[]; onChange?(checked: boolean): void; } interface IState { value: boolean; } export default class SettingsFlag extends React.Component { private readonly id = `mx_SettingsFlag_${secureRandomString(12)}`; public constructor(props: IProps) { super(props); this.state = { value: this.getSettingValue(), }; } public componentDidMount(): void { defaultWatchManager.watchSetting(this.props.name, this.props.roomId ?? null, this.onSettingChange); if (this.props.requires) { // If we have any dependencies for this feature, also watch those features to ensure we catch the disabled state. for (const flag of this.props.requires) { defaultWatchManager.watchSetting(flag, this.props.roomId ?? null, this.onSettingChange); } } } public componentWillUnmount(): void { defaultWatchManager.unwatchSetting(this.onSettingChange); } private getSettingValue(): boolean { // If a level defined in props is overridden by a level at a high precedence, // or the setting lacks support for the desired level, it gets disabled and we should show the overriding value. if ( !SettingsStore.doesSettingSupportLevel(this.props.name, this.props.level) || SettingsStore.settingIsOveriddenAtConfigLevel(this.props.name, this.props.roomId ?? null, this.props.level) ) { return !!SettingsStore.getValue(this.props.name); } return !!SettingsStore.getValueAt( this.props.level, this.props.name, this.props.roomId ?? null, this.props.isExplicit, ); } private onSettingChange = (): void => { this.setState({ value: this.getSettingValue(), }); }; private onChange = async (evt: ChangeEvent): Promise => { const newValue = evt.target.checked; try { await this.save(newValue); } catch (ex) { logger.info(`Failed to save setting ${this.props.name}`, ex); return; } this.setState({ value: newValue }); this.props.onChange?.(newValue); }; private save = async (val?: boolean): Promise => { await SettingsStore.setValue( this.props.name, this.props.roomId ?? null, this.props.level, val !== undefined ? val : this.state.value, ); }; public render(): React.ReactNode { const disabled = !SettingsStore.canSetValue(this.props.name, this.props.roomId ?? null, this.props.level); if (disabled && this.props.hideIfCannotSet) return null; const label = this.props.label ?? SettingsStore.getDisplayName(this.props.name, this.props.level); const description = SettingsStore.getDescription(this.props.name); const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name); const helpMessage = shouldWarn ? _t( "settings|warning", {}, { w: (sub) => {sub}, description, }, ) : description; const disabledMessage = SettingsStore.disabledMessage(this.props.name); return ( ); } }