/* Copyright 2024 New Vector Ltd. Copyright 2019-2023 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ import React, { ReactElement, useCallback, useEffect, useState } from "react"; import { NonEmptyArray } from "../../../../../@types/common"; import { _t, getCurrentLanguage } from "../../../../../languageHandler"; import { UseCase } from "../../../../../settings/enums/UseCase"; import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; import Dropdown from "../../../elements/Dropdown"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import SettingsFlag from "../../../elements/SettingsFlag"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; import { UserTab } from "../../../dialogs/UserTab"; import { OpenToTabPayload } from "../../../../../dispatcher/payloads/OpenToTabPayload"; import { Action } from "../../../../../dispatcher/actions"; import SdkConfig from "../../../../../SdkConfig"; import { showUserOnboardingPage } from "../../../user-onboarding/UserOnboardingPage"; import { SettingsSubsection } from "../../shared/SettingsSubsection"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import LanguageDropdown from "../../../elements/LanguageDropdown"; import PlatformPeg from "../../../../../PlatformPeg"; import { IS_MAC } from "../../../../../Keyboard"; import SpellCheckSettings from "../../SpellCheckSettings"; import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import * as TimezoneHandler from "../../../../../TimezoneHandler"; interface IProps { closeSettingsFn(success: boolean): void; } interface IState { timezone: string | undefined; timezones: string[]; timezoneSearch: string | undefined; autocompleteDelay: string; readMarkerInViewThresholdMs: string; readMarkerOutOfViewThresholdMs: string; } const LanguageSection: React.FC = () => { const [language, setLanguage] = useState(getCurrentLanguage()); const onLanguageChange = useCallback( (newLanguage: string) => { if (language === newLanguage) return; SettingsStore.setValue("language", null, SettingLevel.DEVICE, newLanguage); setLanguage(newLanguage); const platform = PlatformPeg.get(); if (platform) { platform.setLanguage([newLanguage]); platform.reload(); } }, [language], ); return (
{_t("settings|general|application_language")}
{_t("settings|general|application_language_reload_hint")}
); }; const SpellCheckSection: React.FC = () => { const [spellCheckEnabled, setSpellCheckEnabled] = useState(); const [spellCheckLanguages, setSpellCheckLanguages] = useState(); useEffect(() => { (async () => { const plaf = PlatformPeg.get(); const [enabled, langs] = await Promise.all([plaf?.getSpellCheckEnabled(), plaf?.getSpellCheckLanguages()]); setSpellCheckEnabled(enabled); setSpellCheckLanguages(langs || undefined); })(); }, []); const onSpellCheckEnabledChange = useCallback((enabled: boolean) => { setSpellCheckEnabled(enabled); PlatformPeg.get()?.setSpellCheckEnabled(enabled); }, []); const onSpellCheckLanguagesChange = useCallback((languages: string[]): void => { setSpellCheckLanguages(languages); PlatformPeg.get()?.setSpellCheckLanguages(languages); }, []); if (!PlatformPeg.get()?.supportsSpellCheckSettings()) return null; return ( <> {spellCheckEnabled && spellCheckLanguages !== undefined && !IS_MAC && ( )} ); }; export default class PreferencesUserSettingsTab extends React.Component { private static ROOM_LIST_SETTINGS = ["breadcrumbs", "FTUE.userOnboardingButton"]; private static SPACES_SETTINGS = ["Spaces.allRoomsInHome"]; private static KEYBINDINGS_SETTINGS = ["ctrlFForSearch"]; private static PRESENCE_SETTINGS = ["sendReadReceipts", "sendTypingNotifications"]; private static COMPOSER_SETTINGS = [ "MessageComposerInput.autoReplaceEmoji", "MessageComposerInput.useMarkdown", "MessageComposerInput.suggestEmoji", "MessageComposerInput.ctrlEnterToSend", "MessageComposerInput.surroundWith", "MessageComposerInput.showStickersButton", "MessageComposerInput.insertTrailingColon", ]; private static TIME_SETTINGS = ["showTwelveHourTimestamps", "alwaysShowTimestamps"]; private static CODE_BLOCKS_SETTINGS = [ "enableSyntaxHighlightLanguageDetection", "expandCodeByDefault", "showCodeLineNumbers", ]; private static IMAGES_AND_VIDEOS_SETTINGS = ["urlPreviewsEnabled", "autoplayGifs", "autoplayVideo", "showImages"]; private static TIMELINE_SETTINGS = [ "showTypingNotifications", "showRedactions", "showReadReceipts", "showJoinLeaves", "showDisplaynameChanges", "showChatEffects", "showAvatarChanges", "Pill.shouldShowPillAvatar", "TextualBody.enableBigEmoji", "scrollToBottomOnMessageSent", "useOnlyCurrentProfiles", ]; private static ROOM_DIRECTORY_SETTINGS = ["SpotlightSearch.showNsfwPublicRooms"]; private static GENERAL_SETTINGS = [ "promptBeforeInviteUnknownUsers", // Start automatically after startup (electron-only) // Autocomplete delay (niche text box) ]; public constructor(props: IProps) { super(props); this.state = { timezone: TimezoneHandler.getUserTimezone(), timezones: TimezoneHandler.getAllTimezones(), timezoneSearch: undefined, autocompleteDelay: SettingsStore.getValueAt(SettingLevel.DEVICE, "autocompleteDelay").toString(10), readMarkerInViewThresholdMs: SettingsStore.getValueAt( SettingLevel.DEVICE, "readMarkerInViewThresholdMs", ).toString(10), readMarkerOutOfViewThresholdMs: SettingsStore.getValueAt( SettingLevel.DEVICE, "readMarkerOutOfViewThresholdMs", ).toString(10), }; } private onTimezoneChange = (tz: string): void => { this.setState({ timezone: tz }); TimezoneHandler.setUserTimezone(tz); }; /** * If present filter the time zones matching the search term */ private onTimezoneSearchChange = (search: string): void => { const timezoneSearch = search.toLowerCase(); const timezones = timezoneSearch ? TimezoneHandler.getAllTimezones().filter((tz) => { return tz.toLowerCase().includes(timezoneSearch); }) : TimezoneHandler.getAllTimezones(); this.setState({ timezones, timezoneSearch }); }; private onAutocompleteDelayChange = (e: React.ChangeEvent): void => { this.setState({ autocompleteDelay: e.target.value }); SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); }; private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerInViewThresholdMs: e.target.value }); SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); }; private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerOutOfViewThresholdMs: e.target.value }); SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); }; private renderGroup(settingIds: string[], level = SettingLevel.ACCOUNT): React.ReactNodeArray { return settingIds.map((i) => ); } private onKeyboardShortcutsClicked = (): void => { dis.dispatch({ action: Action.ViewUserSettings, initialTabId: UserTab.Keyboard, }); }; public render(): React.ReactNode { const useCase = SettingsStore.getValue("FTUE.useCaseSelection"); const roomListSettings = PreferencesUserSettingsTab.ROOM_LIST_SETTINGS // Only show the user onboarding setting if the user should see the user onboarding page .filter((it) => it !== "FTUE.userOnboardingButton" || showUserOnboardingPage(useCase)); const browserTimezoneLabel: string = _t("settings|preferences|default_timezone", { timezone: TimezoneHandler.shortBrowserTimezone(), }); // Always Preprend the default option const timezones = this.state.timezones.map((tz) => { return
{tz}
; }); timezones.unshift(
{browserTimezoneLabel}
); return ( {/* The heading string is still 'general' from where it was moved, but this section should become 'general' */} {roomListSettings.length > 0 && ( {this.renderGroup(roomListSettings)} )} {this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS, SettingLevel.ACCOUNT)} ( {sub} ), }, )} > {this.renderGroup(PreferencesUserSettingsTab.KEYBINDINGS_SETTINGS)}
{_t("settings|preferences|user_timezone")} {timezones as NonEmptyArray}
{this.renderGroup(PreferencesUserSettingsTab.TIME_SETTINGS)}
{this.renderGroup(PreferencesUserSettingsTab.PRESENCE_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.COMPOSER_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.CODE_BLOCKS_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.IMAGES_AND_VIDEOS_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.TIMELINE_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)} {this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}
); } }