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:
committed by
GitHub
parent
4e1bd69e4d
commit
1e42f28a69
@@ -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 => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)];
|
||||
}),
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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(
|
||||
() =>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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") ?? []);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}, []);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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: [] });
|
||||
|
||||
@@ -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 <></>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user