Harden Settings using mapped types (#28775)

* Harden Settings using mapped types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix issues found during hardening

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove oidc native flow stale key

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2024-12-23 20:25:15 +00:00
committed by GitHub
parent 4e1bd69e4d
commit 1e42f28a69
90 changed files with 576 additions and 274 deletions

View File

@@ -246,7 +246,7 @@ class LoggedInView extends React.Component<IProps, IState> {
} else {
backgroundImage = OwnProfileStore.instance.getHttpAvatarUrl();
}
this.setState({ backgroundImage });
this.setState({ backgroundImage: backgroundImage ?? undefined });
};
public canResetTimelineInRoom = (roomId: string): boolean => {

View File

@@ -20,12 +20,13 @@ import SettingsFlag from "../elements/SettingsFlag";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import InlineSpinner from "../elements/InlineSpinner";
import { shouldShowFeedback } from "../../../utils/Feedback";
import { FeatureSettingKey } from "../../../settings/Settings.tsx";
// XXX: Keep this around for re-use in future Betas
interface IProps {
title?: string;
featureId: string;
featureId: FeatureSettingKey;
}
interface IBetaPillProps {

View File

@@ -282,7 +282,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
}
})();
const developerModeEnabled = useSettingValue<boolean>("developerMode");
const developerModeEnabled = useSettingValue("developerMode");
const developerToolsOption = developerModeEnabled ? (
<DeveloperToolsOption onFinished={onFinished} roomId={room.roomId} />
) : null;

View File

@@ -71,7 +71,7 @@ const showDeleteButton = (canModify: boolean, onDeleteClick: undefined | (() =>
const showSnapshotButton = (widgetMessaging: ClientWidgetApi | undefined): boolean => {
return (
SettingsStore.getValue<boolean>("enableWidgetScreenshots") &&
SettingsStore.getValue("enableWidgetScreenshots") &&
!!widgetMessaging?.hasCapability(MatrixCapabilities.Screenshots)
);
};

View File

@@ -131,7 +131,7 @@ export const AddExistingToSpace: React.FC<IAddExistingToSpaceProps> = ({
onFinished,
}) => {
const cli = useContext(MatrixClientContext);
const msc3946ProcessDynamicPredecessor = useSettingValue<boolean>("feature_dynamic_room_predecessors");
const msc3946ProcessDynamicPredecessor = useSettingValue("feature_dynamic_room_predecessors");
const visibleRooms = useMemo(
() =>
cli

View File

@@ -15,11 +15,12 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "./UserTab";
import GenericFeatureFeedbackDialog from "./GenericFeatureFeedbackDialog";
import { SettingKey } from "../../../settings/Settings.tsx";
// XXX: Keep this around for re-use in future Betas
interface IProps {
featureId: string;
featureId: SettingKey;
onFinished(sendFeedback?: boolean): void;
}
@@ -35,7 +36,7 @@ const BetaFeedbackDialog: React.FC<IProps> = ({ featureId, onFinished }) => {
rageshakeLabel={info.feedbackLabel}
rageshakeData={Object.fromEntries(
(SettingsStore.getBetaInfo(featureId)?.extraSettings || []).map((k) => {
return SettingsStore.getValue(k);
return [k, SettingsStore.getValue(k)];
}),
)}
>

View File

@@ -253,8 +253,8 @@ const ForwardDialog: React.FC<IProps> = ({ matrixClient: cli, event, permalinkCr
const [query, setQuery] = useState("");
const lcQuery = query.toLowerCase();
const previewLayout = useSettingValue<Layout>("layout");
const msc3946DynamicRoomPredecessors = useSettingValue<boolean>("feature_dynamic_room_predecessors");
const previewLayout = useSettingValue("layout");
const msc3946DynamicRoomPredecessors = useSettingValue("feature_dynamic_room_predecessors");
let rooms = useMemo(
() =>

View File

@@ -100,8 +100,8 @@ type ShareDialogProps = XOR<Props, EventProps>;
* A dialog to share a link to a room, user, room member or a matrix event.
*/
export function ShareDialog({ target, customTitle, onFinished, permalinkCreator }: ShareDialogProps): JSX.Element {
const showQrCode = useSettingValue<boolean>(UIFeature.ShareQRCode);
const showSocials = useSettingValue<boolean>(UIFeature.ShareSocial);
const showQrCode = useSettingValue(UIFeature.ShareQRCode);
const showSocials = useSettingValue(UIFeature.ShareSocial);
const timeoutIdRef = useRef<number>();
const [isCopied, setIsCopied] = useState(false);

View File

@@ -85,8 +85,8 @@ function titleForTabID(tabId: UserTab): React.ReactNode {
}
export default function UserSettingsDialog(props: IProps): JSX.Element {
const voipEnabled = useSettingValue<boolean>(UIFeature.Voip);
const mjolnirEnabled = useSettingValue<boolean>("feature_mjolnir");
const voipEnabled = useSettingValue(UIFeature.Voip);
const mjolnirEnabled = useSettingValue("feature_mjolnir");
// store this prop in state as changing tabs back and forth should clear it
const [showMsc4108QrCode, setShowMsc4108QrCode] = useState(props.showMsc4108QrCode);

View File

@@ -15,11 +15,11 @@ import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
import AccessibleButton from "../../elements/AccessibleButton";
import SettingsStore, { LEVEL_ORDER } from "../../../../settings/SettingsStore";
import { SettingLevel } from "../../../../settings/SettingLevel";
import { SETTINGS } from "../../../../settings/Settings";
import { SettingKey, SETTINGS, SettingValueType } from "../../../../settings/Settings";
import Field from "../../elements/Field";
const SettingExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
const [setting, setSetting] = useState<string | null>(null);
const [setting, setSetting] = useState<SettingKey | null>(null);
const [editing, setEditing] = useState(false);
if (setting && editing) {
@@ -36,10 +36,10 @@ const SettingExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
};
return <ViewSetting setting={setting} onBack={onBack} onEdit={onEdit} />;
} else {
const onView = (setting: string): void => {
const onView = (setting: SettingKey): void => {
setSetting(setting);
};
const onEdit = (setting: string): void => {
const onEdit = (setting: SettingKey): void => {
setSetting(setting);
setEditing(true);
};
@@ -50,7 +50,7 @@ const SettingExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
export default SettingExplorer;
interface ICanEditLevelFieldProps {
setting: string;
setting: SettingKey;
level: SettingLevel;
roomId?: string;
}
@@ -65,8 +65,8 @@ const CanEditLevelField: React.FC<ICanEditLevelFieldProps> = ({ setting, roomId,
);
};
function renderExplicitSettingValues(setting: string, roomId?: string): string {
const vals: Record<string, number | null> = {};
function renderExplicitSettingValues(setting: SettingKey, roomId?: string): string {
const vals: Record<string, SettingValueType> = {};
for (const level of LEVEL_ORDER) {
try {
vals[level] = SettingsStore.getValueAt(level, setting, roomId, true, true);
@@ -81,7 +81,7 @@ function renderExplicitSettingValues(setting: string, roomId?: string): string {
}
interface IEditSettingProps extends Pick<IDevtoolsProps, "onBack"> {
setting: string;
setting: SettingKey;
}
const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
@@ -191,7 +191,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
};
interface IViewSettingProps extends Pick<IDevtoolsProps, "onBack"> {
setting: string;
setting: SettingKey;
onEdit(): Promise<void>;
}
@@ -258,7 +258,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
const [query, setQuery] = useState("");
const allSettings = useMemo(() => {
let allSettings = Object.keys(SETTINGS);
let allSettings = Object.keys(SETTINGS) as SettingKey[];
if (query) {
const lcQuery = query.toLowerCase();
allSettings = allSettings.filter((setting) => setting.toLowerCase().includes(lcQuery));

View File

@@ -26,6 +26,7 @@ import {
import TextInputDialog from "../dialogs/TextInputDialog";
import AccessibleButton from "../elements/AccessibleButton";
import withValidation from "../elements/Validation";
import { SettingKey, Settings } from "../../../settings/Settings.tsx";
const SETTING_NAME = "room_directory_servers";
@@ -67,15 +68,32 @@ const validServer = withValidation<undefined, { error?: unknown }>({
memoize: true,
});
function useSettingsValueWithSetter<T>(
settingName: string,
function useSettingsValueWithSetter<S extends SettingKey>(
settingName: S,
level: SettingLevel,
roomId: string | null,
excludeDefault: true,
): [Settings[S]["default"] | undefined, (value: Settings[S]["default"]) => Promise<void>];
function useSettingsValueWithSetter<S extends SettingKey>(
settingName: S,
level: SettingLevel,
roomId?: string | null,
excludeDefault?: false,
): [Settings[S]["default"], (value: Settings[S]["default"]) => Promise<void>];
function useSettingsValueWithSetter<S extends SettingKey>(
settingName: S,
level: SettingLevel,
roomId: string | null = null,
excludeDefault = false,
): [T, (value: T) => Promise<void>] {
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId ?? undefined, excludeDefault));
): [Settings[S]["default"] | undefined, (value: Settings[S]["default"]) => Promise<void>] {
const [value, setValue] = useState(
// XXX: This seems naff but is needed to convince TypeScript that the overload is fine
excludeDefault
? SettingsStore.getValue(settingName, roomId, excludeDefault)
: SettingsStore.getValue(settingName, roomId, excludeDefault),
);
const setter = useCallback(
async (value: T): Promise<void> => {
async (value: Settings[S]["default"]): Promise<void> => {
setValue(value);
SettingsStore.setValue(settingName, roomId, level, value);
},
@@ -84,7 +102,12 @@ function useSettingsValueWithSetter<T>(
useEffect(() => {
const ref = SettingsStore.watchSetting(settingName, roomId, () => {
setValue(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
setValue(
// XXX: This seems naff but is needed to convince TypeScript that the overload is fine
excludeDefault
? SettingsStore.getValue(settingName, roomId, excludeDefault)
: SettingsStore.getValue(settingName, roomId, excludeDefault),
);
});
// clean-up
return () => {
@@ -109,10 +132,7 @@ function removeAll<T>(target: Set<T>, ...toRemove: T[]): void {
}
function useServers(): ServerList {
const [userDefinedServers, setUserDefinedServers] = useSettingsValueWithSetter<string[]>(
SETTING_NAME,
SettingLevel.ACCOUNT,
);
const [userDefinedServers, setUserDefinedServers] = useSettingsValueWithSetter(SETTING_NAME, SettingLevel.ACCOUNT);
const homeServer = MatrixClientPeg.safeGet().getDomain()!;
const configServers = new Set<string>(SdkConfig.getObject("room_directory")?.get("servers") ?? []);

View File

@@ -105,7 +105,7 @@ export default class LanguageDropdown extends React.Component<IProps, IState> {
// default value here too, otherwise we need to handle null / undefined
// values between mounting and the initial value propagating
let language = SettingsStore.getValue<string | undefined>("language", null, /*excludeDefault:*/ true);
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
let value: string | undefined;
if (language) {
value = this.props.value || language;

View File

@@ -15,11 +15,11 @@ import { _t } from "../../../languageHandler";
import ToggleSwitch from "./ToggleSwitch";
import StyledCheckbox from "./StyledCheckbox";
import { SettingLevel } from "../../../settings/SettingLevel";
import { defaultWatchManager } from "../../../settings/Settings";
import { BooleanSettingKey, defaultWatchManager } from "../../../settings/Settings";
interface IProps {
// The setting must be a boolean
name: string;
name: BooleanSettingKey;
level: SettingLevel;
roomId?: string; // for per-room settings
label?: string;

View File

@@ -107,7 +107,7 @@ export default class SpellCheckLanguagesDropdown extends React.Component<
// default value here too, otherwise we need to handle null / undefined;
// values between mounting and the initial value propagating
let language = SettingsStore.getValue<string | undefined>("language", null, /*excludeDefault:*/ true);
let language = SettingsStore.getValue("language", null, /*excludeDefault:*/ true);
let value: string | undefined;
if (language) {
value = this.props.value || language;

View File

@@ -36,9 +36,9 @@ const ExpandCollapseButton: React.FC<{
};
const CodeBlock: React.FC<Props> = ({ children, onHeightChanged }) => {
const enableSyntaxHighlightLanguageDetection = useSettingValue<boolean>("enableSyntaxHighlightLanguageDetection");
const showCodeLineNumbers = useSettingValue<boolean>("showCodeLineNumbers");
const expandCodeByDefault = useSettingValue<boolean>("expandCodeByDefault");
const enableSyntaxHighlightLanguageDetection = useSettingValue("enableSyntaxHighlightLanguageDetection");
const showCodeLineNumbers = useSettingValue("showCodeLineNumbers");
const expandCodeByDefault = useSettingValue("expandCodeByDefault");
const [expanded, setExpanded] = useState(expandCodeByDefault);
let expandCollapseButton: JSX.Element | undefined;

View File

@@ -426,7 +426,7 @@ export default class TextualBody extends React.Component<IBodyProps, IState> {
const stripReply = !mxEvent.replacingEvent() && !!getParentEventId(mxEvent);
const htmlOpts = {
disableBigEmoji: isEmote || !SettingsStore.getValue<boolean>("TextualBody.enableBigEmoji"),
disableBigEmoji: isEmote || !SettingsStore.getValue("TextualBody.enableBigEmoji"),
// Part of Replies fallback support
stripReplyFallback: stripReply,
};

View File

@@ -128,7 +128,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
super(props, context);
this.context = context; // otherwise React will only set it prior to render due to type def above
const isWysiwygLabEnabled = SettingsStore.getValue<boolean>("feature_wysiwyg_composer");
const isWysiwygLabEnabled = SettingsStore.getValue("feature_wysiwyg_composer");
let isRichTextEnabled = true;
let initialComposerContent = "";
if (isWysiwygLabEnabled) {

View File

@@ -54,7 +54,7 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
const matrixClient = useContext(MatrixClientContext);
const { room, narrow } = useScopedRoomContext("room", "narrow");
const isWysiwygLabEnabled = useSettingValue<boolean>("feature_wysiwyg_composer");
const isWysiwygLabEnabled = useSettingValue("feature_wysiwyg_composer");
if (!matrixClient || !room || props.haveRecording) {
return null;

View File

@@ -45,7 +45,7 @@ export function PlainTextComposer({
rightComponent,
eventRelation,
}: PlainTextComposerProps): JSX.Element {
const isAutoReplaceEmojiEnabled = useSettingValue<boolean>("MessageComposerInput.autoReplaceEmoji");
const isAutoReplaceEmojiEnabled = useSettingValue("MessageComposerInput.autoReplaceEmoji");
const {
ref: editorRef,
autocompleteRef,

View File

@@ -61,7 +61,7 @@ export const WysiwygComposer = memo(function WysiwygComposer({
const inputEventProcessor = useInputEventProcessor(onSend, autocompleteRef, initialContent, eventRelation);
const isAutoReplaceEmojiEnabled = useSettingValue<boolean>("MessageComposerInput.autoReplaceEmoji");
const isAutoReplaceEmojiEnabled = useSettingValue("MessageComposerInput.autoReplaceEmoji");
const emojiSuggestions = useMemo(() => getEmojiSuggestions(isAutoReplaceEmojiEnabled), [isAutoReplaceEmojiEnabled]);
const { ref, isWysiwygReady, content, actionStates, wysiwyg, suggestion, messageContent } = useWysiwyg({

View File

@@ -36,7 +36,7 @@ export function useInputEventProcessor(
const roomContext = useScopedRoomContext("liveTimeline", "room", "replyToEvent", "timelineRenderingType");
const composerContext = useComposerContext();
const mxClient = useMatrixClientContext();
const isCtrlEnterToSend = useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const isCtrlEnterToSend = useSettingValue("MessageComposerInput.ctrlEnterToSend");
return useCallback(
(event: WysiwygEvent, composer: Wysiwyg, editor: HTMLElement) => {

View File

@@ -128,7 +128,7 @@ export function usePlainTextListeners(
[eventRelation, mxClient, onInput, roomContext],
);
const enterShouldSend = !useSettingValue<boolean>("MessageComposerInput.ctrlEnterToSend");
const enterShouldSend = !useSettingValue("MessageComposerInput.ctrlEnterToSend");
const onKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
// we need autocomplete to take priority when it is open for using enter to select

View File

@@ -66,7 +66,7 @@ export async function createMessageContent(
// TODO markdown support
const isMarkdownEnabled = SettingsStore.getValue<boolean>("MessageComposerInput.useMarkdown");
const isMarkdownEnabled = SettingsStore.getValue("MessageComposerInput.useMarkdown");
const formattedBody = isHTML ? message : isMarkdownEnabled ? await plainToRich(message, true) : null;
if (formattedBody) {

View File

@@ -47,7 +47,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
super(props);
this.state = {
fontSizeDelta: SettingsStore.getValue<number>("fontSizeDelta", null),
fontSizeDelta: SettingsStore.getValue("fontSizeDelta", null),
browserFontSize: FontWatcher.getBrowserDefaultFontSize(),
useCustomFontSize: SettingsStore.getValue("useCustomFontSize"),
layout: SettingsStore.getValue("layout"),

View File

@@ -70,7 +70,7 @@ interface LayoutRadioProps {
* @param label
*/
function LayoutRadio({ layout, label }: LayoutRadioProps): JSX.Element {
const currentLayout = useSettingValue<Layout>("layout");
const currentLayout = useSettingValue("layout");
const eventTileInfo = useEventTileInfo();
return (
@@ -134,8 +134,8 @@ function useEventTileInfo(): EventTileInfo {
* A toggleable setting to enable or disable the compact layout.
*/
function ToggleCompactLayout(): JSX.Element {
const compactLayoutEnabled = useSettingValue<boolean>("useCompactLayout");
const layout = useSettingValue<Layout>("layout");
const compactLayoutEnabled = useSettingValue("useCompactLayout");
const layout = useSettingValue("layout");
return (
<Root

View File

@@ -40,7 +40,7 @@ import { useSettingValue } from "../../../hooks/useSettings";
export function ThemeChoicePanel(): JSX.Element {
const themeState = useTheme();
const themeWatcher = useRef(new ThemeWatcher());
const customThemeEnabled = useSettingValue<boolean>("feature_custom_themes");
const customThemeEnabled = useSettingValue("feature_custom_themes");
return (
<SettingsSubsection heading={_t("common|theme")} legacy={false} data-testid="themePanel">
@@ -159,7 +159,7 @@ function ThemeSelectors({ theme, disabled }: ThemeSelectorProps): JSX.Element {
* Return all the available themes
*/
function useThemes(): Array<ITheme & { isDark: boolean }> {
const customThemes = useSettingValue<CustomThemeType[] | undefined>("custom_themes");
const customThemes = useSettingValue("custom_themes");
return useMemo(() => {
// Put the custom theme into a map
// To easily find the theme by name when going through the themes list
@@ -239,8 +239,7 @@ function CustomTheme({ theme }: CustomThemeProps): JSX.Element {
// Get the custom themes and do a cheap clone
// To avoid to mutate the original array in the settings
const currentThemes =
SettingsStore.getValue<CustomThemeType[]>("custom_themes").map((t) => t) || [];
const currentThemes = SettingsStore.getValue("custom_themes").map((t) => t) || [];
try {
const r = await fetch(customTheme);
@@ -294,7 +293,7 @@ interface CustomThemeListProps {
* List of the custom themes
*/
function CustomThemeList({ theme: currentTheme }: CustomThemeListProps): JSX.Element {
const customThemes = useSettingValue<CustomThemeType[]>("custom_themes") || [];
const customThemes = useSettingValue("custom_themes") || [];
return (
<ul className="mx_ThemeChoicePanel_CustomThemeList">
@@ -309,8 +308,7 @@ function CustomThemeList({ theme: currentTheme }: CustomThemeListProps): JSX.Ele
onClick={async () => {
// Get the custom themes and do a cheap clone
// To avoid to mutate the original array in the settings
const currentThemes =
SettingsStore.getValue<CustomThemeType[]>("custom_themes").map((t) => t) || [];
const currentThemes = SettingsStore.getValue("custom_themes").map((t) => t) || [];
// Remove the theme from the list
const newThemes = currentThemes.filter((t) => t.name !== theme.name);

View File

@@ -70,9 +70,9 @@ function useHasUnreadNotifications(): boolean {
export default function NotificationSettings2(): JSX.Element {
const cli = useMatrixClientContext();
const desktopNotifications = useSettingValue<boolean>("notificationsEnabled");
const desktopShowBody = useSettingValue<boolean>("notificationBodyEnabled");
const audioNotifications = useSettingValue<boolean>("audioNotificationsEnabled");
const desktopNotifications = useSettingValue("notificationsEnabled");
const desktopShowBody = useSettingValue("notificationBodyEnabled");
const audioNotifications = useSettingValue("audioNotificationsEnabled");
const { model, hasPendingChanges, reconcile } = useNotificationSettings(cli);

View File

@@ -14,7 +14,7 @@ import { SettingLevel } from "../../../../../settings/SettingLevel";
import SdkConfig from "../../../../../SdkConfig";
import BetaCard from "../../../beta/BetaCard";
import SettingsFlag from "../../../elements/SettingsFlag";
import { LabGroup, labGroupNames } from "../../../../../settings/Settings";
import { FeatureSettingKey, LabGroup, labGroupNames } from "../../../../../settings/Settings";
import { EnhancedMap } from "../../../../../utils/maps";
import { SettingsSection } from "../../shared/SettingsSection";
import { SettingsSubsection, SettingsSubsectionText } from "../../shared/SettingsSubsection";
@@ -25,8 +25,8 @@ export const showLabsFlags = (): boolean => {
};
export default class LabsUserSettingsTab extends React.Component<{}> {
private readonly labs: string[];
private readonly betas: string[];
private readonly labs: FeatureSettingKey[];
private readonly betas: FeatureSettingKey[];
public constructor(props: {}) {
super(props);
@@ -34,10 +34,10 @@ export default class LabsUserSettingsTab extends React.Component<{}> {
const features = SettingsStore.getFeatureSettingNames();
const [labs, betas] = features.reduce(
(arr, f) => {
arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f);
arr[SettingsStore.getBetaInfo(f) ? 1 : 0].push(f as FeatureSettingKey);
return arr;
},
[[], []] as [string[], string[]],
[[], []] as [FeatureSettingKey[], FeatureSettingKey[]],
);
this.labs = labs;

View File

@@ -11,7 +11,6 @@ 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";
@@ -33,6 +32,7 @@ import { IS_MAC } from "../../../../../Keyboard";
import SpellCheckSettings from "../../SpellCheckSettings";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import * as TimezoneHandler from "../../../../../TimezoneHandler";
import { BooleanSettingKey } from "../../../../../settings/Settings.tsx";
interface IProps {
closeSettingsFn(success: boolean): void;
@@ -117,15 +117,15 @@ const SpellCheckSection: React.FC = () => {
};
export default class PreferencesUserSettingsTab extends React.Component<IProps, IState> {
private static ROOM_LIST_SETTINGS = ["breadcrumbs", "FTUE.userOnboardingButton"];
private static ROOM_LIST_SETTINGS: BooleanSettingKey[] = ["breadcrumbs", "FTUE.userOnboardingButton"];
private static SPACES_SETTINGS = ["Spaces.allRoomsInHome"];
private static SPACES_SETTINGS: BooleanSettingKey[] = ["Spaces.allRoomsInHome"];
private static KEYBINDINGS_SETTINGS = ["ctrlFForSearch"];
private static KEYBINDINGS_SETTINGS: BooleanSettingKey[] = ["ctrlFForSearch"];
private static PRESENCE_SETTINGS = ["sendReadReceipts", "sendTypingNotifications"];
private static PRESENCE_SETTINGS: BooleanSettingKey[] = ["sendReadReceipts", "sendTypingNotifications"];
private static COMPOSER_SETTINGS = [
private static COMPOSER_SETTINGS: BooleanSettingKey[] = [
"MessageComposerInput.autoReplaceEmoji",
"MessageComposerInput.useMarkdown",
"MessageComposerInput.suggestEmoji",
@@ -135,17 +135,22 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
"MessageComposerInput.insertTrailingColon",
];
private static TIME_SETTINGS = ["showTwelveHourTimestamps", "alwaysShowTimestamps"];
private static TIME_SETTINGS: BooleanSettingKey[] = ["showTwelveHourTimestamps", "alwaysShowTimestamps"];
private static CODE_BLOCKS_SETTINGS = [
private static CODE_BLOCKS_SETTINGS: BooleanSettingKey[] = [
"enableSyntaxHighlightLanguageDetection",
"expandCodeByDefault",
"showCodeLineNumbers",
];
private static IMAGES_AND_VIDEOS_SETTINGS = ["urlPreviewsEnabled", "autoplayGifs", "autoplayVideo", "showImages"];
private static IMAGES_AND_VIDEOS_SETTINGS: BooleanSettingKey[] = [
"urlPreviewsEnabled",
"autoplayGifs",
"autoplayVideo",
"showImages",
];
private static TIMELINE_SETTINGS = [
private static TIMELINE_SETTINGS: BooleanSettingKey[] = [
"showTypingNotifications",
"showRedactions",
"showReadReceipts",
@@ -159,9 +164,9 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
"useOnlyCurrentProfiles",
];
private static ROOM_DIRECTORY_SETTINGS = ["SpotlightSearch.showNsfwPublicRooms"];
private static ROOM_DIRECTORY_SETTINGS: BooleanSettingKey[] = ["SpotlightSearch.showNsfwPublicRooms"];
private static GENERAL_SETTINGS = [
private static GENERAL_SETTINGS: BooleanSettingKey[] = [
"promptBeforeInviteUnknownUsers",
// Start automatically after startup (electron-only)
// Autocomplete delay (niche text box)
@@ -220,7 +225,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value);
};
private renderGroup(settingIds: string[], level = SettingLevel.ACCOUNT): React.ReactNodeArray {
private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): React.ReactNodeArray {
return settingIds.map((i) => <SettingsFlag key={i} name={i} level={level} />);
}
@@ -232,7 +237,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
};
public render(): React.ReactNode {
const useCase = SettingsStore.getValue<UseCase | null>("FTUE.useCaseSelection");
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));

View File

@@ -58,8 +58,8 @@ const SidebarUserSettingsTab: React.FC = () => {
[MetaSpace.People]: peopleEnabled,
[MetaSpace.Orphans]: orphansEnabled,
[MetaSpace.VideoRooms]: videoRoomsEnabled,
} = useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
const allRoomsInHome = useSettingValue<boolean>("Spaces.allRoomsInHome");
} = useSettingValue("Spaces.enabledMetaSpaces");
const allRoomsInHome = useSettingValue("Spaces.allRoomsInHome");
const guestSpaUrl = useMemo(() => {
return SdkConfig.get("element_call").guest_spa_url;
}, []);

View File

@@ -36,10 +36,10 @@ const QuickSettingsButton: React.FC<{
const [menuDisplayed, handle, openMenu, closeMenu] = useContextMenu<HTMLDivElement>();
const { [MetaSpace.Favourites]: favouritesEnabled, [MetaSpace.People]: peopleEnabled } =
useSettingValue<Record<MetaSpace, boolean>>("Spaces.enabledMetaSpaces");
useSettingValue("Spaces.enabledMetaSpaces");
const currentRoomId = SdkContextClass.instance.roomViewStore.getRoomId();
const developerModeEnabled = useSettingValue<boolean>("developerMode");
const developerModeEnabled = useSettingValue("developerMode");
let contextMenu: JSX.Element | undefined;
if (menuDisplayed && handle.current) {

View File

@@ -88,7 +88,7 @@ export const HomeButtonContextMenu: React.FC<ComponentProps<typeof SpaceContextM
hideHeader,
...props
}) => {
const allRoomsInHome = useSettingValue<boolean>("Spaces.allRoomsInHome");
const allRoomsInHome = useSettingValue("Spaces.allRoomsInHome");
return (
<IconizedContextMenu {...props} onFinished={onFinished} className="mx_SpacePanel_contextMenu" compact>

View File

@@ -44,7 +44,7 @@ export function ThreadsActivityCentre({ displayButtonLabel }: ThreadsActivityCen
const [open, setOpen] = useState(false);
const roomsAndNotifications = useUnreadThreadRooms(open);
const isReleaseAnnouncementOpen = useIsReleaseAnnouncementOpen("threadsActivityCentre");
const settingTACOnlyNotifs = useSettingValue<boolean>("Notifications.tac_only_notifications");
const settingTACOnlyNotifs = useSettingValue("Notifications.tac_only_notifications");
const emptyCaption = settingTACOnlyNotifs
? _t("threads_activity_centre|no_rooms_with_threads_notifs")

View File

@@ -32,8 +32,8 @@ type Result = {
* @returns {Result}
*/
export function useUnreadThreadRooms(forceComputation: boolean): Result {
const msc3946ProcessDynamicPredecessor = useSettingValue<boolean>("feature_dynamic_room_predecessors");
const settingTACOnlyNotifs = useSettingValue<boolean>("Notifications.tac_only_notifications");
const msc3946ProcessDynamicPredecessor = useSettingValue("feature_dynamic_room_predecessors");
const settingTACOnlyNotifs = useSettingValue("Notifications.tac_only_notifications");
const mxClient = useMatrixClientContext();
const [result, setResult] = useState<Result>({ greatestNotificationLevel: NotificationLevel.None, rooms: [] });

View File

@@ -14,7 +14,6 @@ import defaultDispatcher from "../../../dispatcher/dispatcher";
import { useSettingValue } from "../../../hooks/useSettings";
import { _t } from "../../../languageHandler";
import PosthogTrackers from "../../../PosthogTrackers";
import { UseCase } from "../../../settings/enums/UseCase";
import { SettingLevel } from "../../../settings/SettingLevel";
import SettingsStore from "../../../settings/SettingsStore";
import AccessibleButton, { ButtonEvent } from "../../views/elements/AccessibleButton";
@@ -27,8 +26,8 @@ interface Props {
}
export function UserOnboardingButton({ selected, minimized }: Props): JSX.Element {
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection");
const visible = useSettingValue<boolean>("FTUE.userOnboardingButton");
const useCase = useSettingValue("FTUE.useCaseSelection");
const visible = useSettingValue("FTUE.userOnboardingButton");
if (!visible || minimized || !showUserOnboardingPage(useCase)) {
return <></>;

View File

@@ -41,7 +41,7 @@ export function UserOnboardingPage({ justRegistered = false }: Props): JSX.Eleme
const config = SdkConfig.get();
const pageUrl = getHomePageUrl(config, cli);
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection");
const useCase = useSettingValue("FTUE.useCaseSelection");
const context = useUserOnboardingContext();
const tasks = useUserOnboardingTasks(context);