mirror of https://github.com/vector-im/riot-web
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>pull/28813/head
parent
4e1bd69e4d
commit
1e42f28a69
|
@ -19,6 +19,10 @@ ignore.push("/OpenSpotlightPayload.ts");
|
|||
ignore.push("/PinnedMessageBadge.tsx");
|
||||
ignore.push("/editor/mock.ts");
|
||||
ignore.push("DeviceIsolationModeController.ts");
|
||||
ignore.push("/json.ts");
|
||||
ignore.push("/ReleaseAnnouncementStore.ts");
|
||||
ignore.push("/WidgetLayoutStore.ts");
|
||||
ignore.push("/common.ts");
|
||||
|
||||
// We ignore js-sdk by default as it may export for other non element-web projects
|
||||
if (!includeJSSDK) ignore.push("matrix-js-sdk");
|
||||
|
|
|
@ -44,3 +44,11 @@ type DeepReadonlyObject<T> = {
|
|||
};
|
||||
|
||||
export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];
|
||||
|
||||
/**
|
||||
* Returns a union type of the keys of the input Object type whose values are assignable to the given Item type.
|
||||
* Based on https://stackoverflow.com/a/57862073
|
||||
*/
|
||||
export type Assignable<Object, Item> = {
|
||||
[Key in keyof Object]: Object[Key] extends Item ? Key : never;
|
||||
}[keyof Object];
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
export type JsonValue = null | string | number | boolean;
|
||||
export type JsonArray = Array<JsonValue | JsonObject | JsonArray>;
|
||||
export interface JsonObject {
|
||||
[key: string]: JsonObject | JsonArray | JsonValue;
|
||||
}
|
||||
export type Json = JsonArray | JsonObject;
|
|
@ -176,7 +176,7 @@ class NotifierClass extends TypedEventEmitter<keyof EmittedEvents, EmittedEvents
|
|||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
size: string;
|
||||
size: number;
|
||||
} | null {
|
||||
// We do no caching here because the SDK caches setting
|
||||
// and the browser will cache the sound.
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ interface ILegacyFormat {
|
|||
}
|
||||
|
||||
// New format tries to be more space efficient for synchronization. Ordered by Date descending.
|
||||
type Format = [string, number][]; // [emoji, count]
|
||||
export type RecentEmojiData = [emoji: string, count: number][];
|
||||
|
||||
const SETTING_NAME = "recent_emoji";
|
||||
|
||||
|
@ -33,7 +33,7 @@ function migrate(): void {
|
|||
SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, newFormat.slice(0, STORAGE_LIMIT));
|
||||
}
|
||||
|
||||
function getRecentEmoji(): Format {
|
||||
function getRecentEmoji(): RecentEmojiData {
|
||||
return SettingsStore.getValue(SETTING_NAME) || [];
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { filterBoolean } from "../../utils/arrays";
|
|||
export const useRecentSearches = (): [Room[], () => void] => {
|
||||
const [rooms, setRooms] = useState(() => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const recents = SettingsStore.getValue<string[]>("SpotlightSearch.recentSearches", null);
|
||||
const recents = SettingsStore.getValue("SpotlightSearch.recentSearches", null);
|
||||
return filterBoolean(recents.map((r) => cli.getRoom(r)));
|
||||
});
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ export const usePublicRoomDirectory = (): {
|
|||
|
||||
const [updateQuery, updateResult] = useLatestResult<IRoomDirectoryOptions, IPublicRoomsChunkRoom[]>(setPublicRooms);
|
||||
|
||||
const showNsfwPublicRooms = useSettingValue<boolean>("SpotlightSearch.showNsfwPublicRooms");
|
||||
const showNsfwPublicRooms = useSettingValue("SpotlightSearch.showNsfwPublicRooms");
|
||||
|
||||
async function initProtocols(): Promise<void> {
|
||||
if (!MatrixClientPeg.get()) {
|
||||
|
|
|
@ -10,14 +10,39 @@ import { useEffect, useState } from "react";
|
|||
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { SettingLevel } from "../settings/SettingLevel";
|
||||
import { FeatureSettingKey, SettingKey, Settings } from "../settings/Settings.tsx";
|
||||
|
||||
// Hook to fetch the value of a setting and dynamically update when it changes
|
||||
export const useSettingValue = <T>(settingName: string, roomId: string | null = null, excludeDefault = false): T => {
|
||||
const [value, setValue] = useState(SettingsStore.getValue<T>(settingName, roomId, excludeDefault));
|
||||
export function useSettingValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
excludeDefault: true,
|
||||
): Settings[S]["default"] | undefined;
|
||||
export function useSettingValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId?: string | null,
|
||||
excludeDefault?: false,
|
||||
): Settings[S]["default"];
|
||||
export function useSettingValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
excludeDefault = false,
|
||||
): Settings[S]["default"] | undefined {
|
||||
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),
|
||||
);
|
||||
|
||||
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 () => {
|
||||
|
@ -26,7 +51,7 @@ export const useSettingValue = <T>(settingName: string, roomId: string | null =
|
|||
}, [settingName, roomId, excludeDefault]);
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch the value of a setting at a specific level and dynamically update when it changes
|
||||
|
@ -37,20 +62,18 @@ export const useSettingValue = <T>(settingName: string, roomId: string | null =
|
|||
* @param explicit
|
||||
* @param excludeDefault
|
||||
*/
|
||||
export const useSettingValueAt = <T>(
|
||||
export const useSettingValueAt = <S extends SettingKey>(
|
||||
level: SettingLevel,
|
||||
settingName: string,
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
explicit = false,
|
||||
excludeDefault = false,
|
||||
): T => {
|
||||
const [value, setValue] = useState(
|
||||
SettingsStore.getValueAt<T>(level, settingName, roomId, explicit, excludeDefault),
|
||||
);
|
||||
): Settings[S]["default"] => {
|
||||
const [value, setValue] = useState(SettingsStore.getValueAt(level, settingName, roomId, explicit, excludeDefault));
|
||||
|
||||
useEffect(() => {
|
||||
const ref = SettingsStore.watchSetting(settingName, roomId, () => {
|
||||
setValue(SettingsStore.getValueAt<T>(level, settingName, roomId, explicit, excludeDefault));
|
||||
setValue(SettingsStore.getValueAt(level, settingName, roomId, explicit, excludeDefault));
|
||||
});
|
||||
// clean-up
|
||||
return () => {
|
||||
|
@ -62,8 +85,8 @@ export const useSettingValueAt = <T>(
|
|||
};
|
||||
|
||||
// Hook to fetch whether a feature is enabled and dynamically update when that changes
|
||||
export const useFeatureEnabled = (featureName: string, roomId: string | null = null): boolean => {
|
||||
const [enabled, setEnabled] = useState(SettingsStore.getValue<boolean>(featureName, roomId));
|
||||
export const useFeatureEnabled = (featureName: FeatureSettingKey, roomId: string | null = null): boolean => {
|
||||
const [enabled, setEnabled] = useState(SettingsStore.getValue(featureName, roomId));
|
||||
|
||||
useEffect(() => {
|
||||
const ref = SettingsStore.watchSetting(featureName, roomId, () => {
|
||||
|
|
|
@ -16,10 +16,10 @@ export function useTheme(): { theme: string; systemThemeActivated: boolean } {
|
|||
// We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we
|
||||
// show the right values for things.
|
||||
|
||||
const themeChoice = useSettingValue<string>("theme");
|
||||
const systemThemeExplicit = useSettingValueAt<string>(SettingLevel.DEVICE, "use_system_theme", null, false, true);
|
||||
const themeExplicit = useSettingValueAt<string>(SettingLevel.DEVICE, "theme", null, false, true);
|
||||
const systemThemeActivated = useSettingValue<boolean>("use_system_theme");
|
||||
const themeChoice = useSettingValue("theme");
|
||||
const systemThemeExplicit = useSettingValueAt(SettingLevel.DEVICE, "use_system_theme", null, false, true);
|
||||
const themeExplicit = useSettingValueAt(SettingLevel.DEVICE, "theme", null, false, true);
|
||||
const systemThemeActivated = useSettingValue("use_system_theme");
|
||||
|
||||
// If the user has enabled system theme matching, use that.
|
||||
if (systemThemeExplicit) {
|
||||
|
|
|
@ -145,7 +145,7 @@ const tasks: UserOnboardingTask[] = [
|
|||
];
|
||||
|
||||
export function useUserOnboardingTasks(context: UserOnboardingContext): UserOnboardingTaskWithResolvedCompletion[] {
|
||||
const useCase = useSettingValue<UseCase | null>("FTUE.useCaseSelection") ?? UseCase.Skip;
|
||||
const useCase = useSettingValue("FTUE.useCaseSelection") ?? UseCase.Skip;
|
||||
|
||||
return useMemo<UserOnboardingTaskWithResolvedCompletion[]>(() => {
|
||||
return tasks
|
||||
|
|
|
@ -688,7 +688,7 @@ export class ElementCall extends Call {
|
|||
|
||||
// Set custom fonts
|
||||
if (SettingsStore.getValue("useSystemFont")) {
|
||||
SettingsStore.getValue<string>("systemFont")
|
||||
SettingsStore.getValue("systemFont")
|
||||
.split(",")
|
||||
.map((font) => {
|
||||
// Strip whitespace and quotes
|
||||
|
|
|
@ -38,6 +38,13 @@ import { WatchManager } from "./WatchManager";
|
|||
import { CustomTheme } from "../theme";
|
||||
import AnalyticsController from "./controllers/AnalyticsController";
|
||||
import FallbackIceServerController from "./controllers/FallbackIceServerController";
|
||||
import { UseCase } from "./enums/UseCase.tsx";
|
||||
import { IRightPanelForRoomStored } from "../stores/right-panel/RightPanelStoreIPanelState.ts";
|
||||
import { ILayoutSettings } from "../stores/widgets/WidgetLayoutStore.ts";
|
||||
import { ReleaseAnnouncementData } from "../stores/ReleaseAnnouncementStore.ts";
|
||||
import { Json, JsonValue } from "../@types/json.ts";
|
||||
import { RecentEmojiData } from "../emojipicker/recent.ts";
|
||||
import { Assignable } from "../@types/common.ts";
|
||||
|
||||
export const defaultWatchManager = new WatchManager();
|
||||
|
||||
|
@ -106,15 +113,7 @@ export const labGroupNames: Record<LabGroup, TranslationKey> = {
|
|||
[LabGroup.Ui]: _td("labs|group_ui"),
|
||||
};
|
||||
|
||||
export type SettingValueType =
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| number[]
|
||||
| string[]
|
||||
| Record<string, unknown>
|
||||
| Record<string, unknown>[]
|
||||
| null;
|
||||
export type SettingValueType = Json | JsonValue | Record<string, unknown> | Record<string, unknown>[];
|
||||
|
||||
export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
|
||||
isFeature?: false | undefined;
|
||||
|
@ -164,7 +163,7 @@ export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
|
|||
image?: string; // require(...)
|
||||
feedbackSubheading?: TranslationKey;
|
||||
feedbackLabel?: string;
|
||||
extraSettings?: string[];
|
||||
extraSettings?: BooleanSettingKey[];
|
||||
requiresRefresh?: boolean;
|
||||
};
|
||||
|
||||
|
@ -181,7 +180,179 @@ export interface IFeature extends Omit<IBaseSetting<boolean>, "isFeature"> {
|
|||
// Type using I-identifier for backwards compatibility from before it became a discriminated union
|
||||
export type ISetting = IBaseSetting | IFeature;
|
||||
|
||||
export const SETTINGS: { [setting: string]: ISetting } = {
|
||||
export interface Settings {
|
||||
[settingName: `UIFeature.${string}`]: IBaseSetting<boolean>;
|
||||
|
||||
// We can't use the following type because of `feature_sliding_sync_proxy_url` & `feature_hidebold` being in the namespace incorrectly
|
||||
// [settingName: `feature_${string}`]: IFeature;
|
||||
"feature_video_rooms": IFeature;
|
||||
[Features.NotificationSettings2]: IFeature;
|
||||
[Features.ReleaseAnnouncement]: IFeature;
|
||||
"feature_msc3531_hide_messages_pending_moderation": IFeature;
|
||||
"feature_report_to_moderators": IFeature;
|
||||
"feature_latex_maths": IFeature;
|
||||
"feature_wysiwyg_composer": IFeature;
|
||||
"feature_mjolnir": IFeature;
|
||||
"feature_custom_themes": IFeature;
|
||||
"feature_exclude_insecure_devices": IFeature;
|
||||
"feature_html_topic": IFeature;
|
||||
"feature_bridge_state": IFeature;
|
||||
"feature_jump_to_date": IFeature;
|
||||
"feature_sliding_sync": IFeature;
|
||||
"feature_element_call_video_rooms": IFeature;
|
||||
"feature_group_calls": IFeature;
|
||||
"feature_disable_call_per_sender_encryption": IFeature;
|
||||
"feature_allow_screen_share_only_mode": IFeature;
|
||||
"feature_location_share_live": IFeature;
|
||||
"feature_dynamic_room_predecessors": IFeature;
|
||||
"feature_render_reaction_images": IFeature;
|
||||
"feature_ask_to_join": IFeature;
|
||||
"feature_notifications": IFeature;
|
||||
// These are in the feature namespace but aren't actually features
|
||||
"feature_sliding_sync_proxy_url": IBaseSetting<string>;
|
||||
"feature_hidebold": IBaseSetting<boolean>;
|
||||
|
||||
"useOnlyCurrentProfiles": IBaseSetting<boolean>;
|
||||
"mjolnirRooms": IBaseSetting<string[]>;
|
||||
"mjolnirPersonalRoom": IBaseSetting<string | null>;
|
||||
"RoomList.backgroundImage": IBaseSetting<string | null>;
|
||||
"sendReadReceipts": IBaseSetting<boolean>;
|
||||
"baseFontSize": IBaseSetting<"" | number>;
|
||||
"baseFontSizeV2": IBaseSetting<"" | number>;
|
||||
"fontSizeDelta": IBaseSetting<number>;
|
||||
"useCustomFontSize": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.suggestEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.showStickersButton": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.showPollsButton": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.insertTrailingColon": IBaseSetting<boolean>;
|
||||
"Notifications.alwaysShowBadgeCounts": IBaseSetting<boolean>;
|
||||
"Notifications.showbold": IBaseSetting<boolean>;
|
||||
"Notifications.tac_only_notifications": IBaseSetting<boolean>;
|
||||
"useCompactLayout": IBaseSetting<boolean>;
|
||||
"showRedactions": IBaseSetting<boolean>;
|
||||
"showJoinLeaves": IBaseSetting<boolean>;
|
||||
"showAvatarChanges": IBaseSetting<boolean>;
|
||||
"showDisplaynameChanges": IBaseSetting<boolean>;
|
||||
"showReadReceipts": IBaseSetting<boolean>;
|
||||
"showTwelveHourTimestamps": IBaseSetting<boolean>;
|
||||
"alwaysShowTimestamps": IBaseSetting<boolean>;
|
||||
"userTimezone": IBaseSetting<string>;
|
||||
"userTimezonePublish": IBaseSetting<boolean>;
|
||||
"autoplayGifs": IBaseSetting<boolean>;
|
||||
"autoplayVideo": IBaseSetting<boolean>;
|
||||
"enableSyntaxHighlightLanguageDetection": IBaseSetting<boolean>;
|
||||
"expandCodeByDefault": IBaseSetting<boolean>;
|
||||
"showCodeLineNumbers": IBaseSetting<boolean>;
|
||||
"scrollToBottomOnMessageSent": IBaseSetting<boolean>;
|
||||
"Pill.shouldShowPillAvatar": IBaseSetting<boolean>;
|
||||
"TextualBody.enableBigEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.isRichTextEnabled": IBaseSetting<boolean>;
|
||||
"MessageComposer.showFormatting": IBaseSetting<boolean>;
|
||||
"sendTypingNotifications": IBaseSetting<boolean>;
|
||||
"showTypingNotifications": IBaseSetting<boolean>;
|
||||
"ctrlFForSearch": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.ctrlEnterToSend": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.surroundWith": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.autoReplaceEmoji": IBaseSetting<boolean>;
|
||||
"MessageComposerInput.useMarkdown": IBaseSetting<boolean>;
|
||||
"VideoView.flipVideoHorizontally": IBaseSetting<boolean>;
|
||||
"theme": IBaseSetting<string>;
|
||||
"custom_themes": IBaseSetting<CustomTheme[]>;
|
||||
"use_system_theme": IBaseSetting<boolean>;
|
||||
"useBundledEmojiFont": IBaseSetting<boolean>;
|
||||
"useSystemFont": IBaseSetting<boolean>;
|
||||
"systemFont": IBaseSetting<string>;
|
||||
"webRtcAllowPeerToPeer": IBaseSetting<boolean>;
|
||||
"webrtc_audiooutput": IBaseSetting<string>;
|
||||
"webrtc_audioinput": IBaseSetting<string>;
|
||||
"webrtc_videoinput": IBaseSetting<string>;
|
||||
"webrtc_audio_autoGainControl": IBaseSetting<boolean>;
|
||||
"webrtc_audio_echoCancellation": IBaseSetting<boolean>;
|
||||
"webrtc_audio_noiseSuppression": IBaseSetting<boolean>;
|
||||
"language": IBaseSetting<string>;
|
||||
"breadcrumb_rooms": IBaseSetting<string[]>;
|
||||
"recent_emoji": IBaseSetting<RecentEmojiData>;
|
||||
"SpotlightSearch.recentSearches": IBaseSetting<string[]>;
|
||||
"SpotlightSearch.showNsfwPublicRooms": IBaseSetting<boolean>;
|
||||
"room_directory_servers": IBaseSetting<string[]>;
|
||||
"integrationProvisioning": IBaseSetting<boolean>;
|
||||
"allowedWidgets": IBaseSetting<{ [eventId: string]: boolean }>;
|
||||
"analyticsOptIn": IBaseSetting<boolean>;
|
||||
"pseudonymousAnalyticsOptIn": IBaseSetting<boolean | null>;
|
||||
"deviceClientInformationOptIn": IBaseSetting<boolean>;
|
||||
"FTUE.useCaseSelection": IBaseSetting<UseCase | null>;
|
||||
"Registration.mobileRegistrationHelper": IBaseSetting<boolean>;
|
||||
"autocompleteDelay": IBaseSetting<number>;
|
||||
"readMarkerInViewThresholdMs": IBaseSetting<number>;
|
||||
"readMarkerOutOfViewThresholdMs": IBaseSetting<number>;
|
||||
"blacklistUnverifiedDevices": IBaseSetting<boolean>;
|
||||
"urlPreviewsEnabled": IBaseSetting<boolean>;
|
||||
"urlPreviewsEnabled_e2ee": IBaseSetting<boolean>;
|
||||
"notificationsEnabled": IBaseSetting<boolean>;
|
||||
"deviceNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"notificationSound": IBaseSetting<
|
||||
| {
|
||||
name: string;
|
||||
type: string;
|
||||
size: number;
|
||||
url: string;
|
||||
}
|
||||
| false
|
||||
>;
|
||||
"notificationBodyEnabled": IBaseSetting<boolean>;
|
||||
"audioNotificationsEnabled": IBaseSetting<boolean>;
|
||||
"enableWidgetScreenshots": IBaseSetting<boolean>;
|
||||
"promptBeforeInviteUnknownUsers": IBaseSetting<boolean>;
|
||||
"widgetOpenIDPermissions": IBaseSetting<{
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
}>;
|
||||
"breadcrumbs": IBaseSetting<boolean>;
|
||||
"FTUE.userOnboardingButton": IBaseSetting<boolean>;
|
||||
"showHiddenEventsInTimeline": IBaseSetting<boolean>;
|
||||
"lowBandwidth": IBaseSetting<boolean>;
|
||||
"fallbackICEServerAllowed": IBaseSetting<boolean | null>;
|
||||
"showImages": IBaseSetting<boolean>;
|
||||
"RightPanel.phasesGlobal": IBaseSetting<IRightPanelForRoomStored | null>;
|
||||
"RightPanel.phases": IBaseSetting<IRightPanelForRoomStored | null>;
|
||||
"enableEventIndexing": IBaseSetting<boolean>;
|
||||
"crawlerSleepTime": IBaseSetting<number>;
|
||||
"showCallButtonsInComposer": IBaseSetting<boolean>;
|
||||
"ircDisplayNameWidth": IBaseSetting<number>;
|
||||
"layout": IBaseSetting<Layout>;
|
||||
"Images.size": IBaseSetting<ImageSize>;
|
||||
"showChatEffects": IBaseSetting<boolean>;
|
||||
"Performance.addSendMessageTimingMetadata": IBaseSetting<boolean>;
|
||||
"Widgets.pinned": IBaseSetting<{ [widgetId: string]: boolean }>;
|
||||
"Widgets.layout": IBaseSetting<ILayoutSettings | null>;
|
||||
"Spaces.allRoomsInHome": IBaseSetting<boolean>;
|
||||
"Spaces.enabledMetaSpaces": IBaseSetting<Partial<Record<MetaSpace, boolean>>>;
|
||||
"Spaces.showPeopleInSpace": IBaseSetting<boolean>;
|
||||
"developerMode": IBaseSetting<boolean>;
|
||||
"automaticErrorReporting": IBaseSetting<boolean>;
|
||||
"automaticDecryptionErrorReporting": IBaseSetting<boolean>;
|
||||
"automaticKeyBackNotEnabledReporting": IBaseSetting<boolean>;
|
||||
"debug_scroll_panel": IBaseSetting<boolean>;
|
||||
"debug_timeline_panel": IBaseSetting<boolean>;
|
||||
"debug_registration": IBaseSetting<boolean>;
|
||||
"debug_animation": IBaseSetting<boolean>;
|
||||
"debug_legacy_call_handler": IBaseSetting<boolean>;
|
||||
"audioInputMuted": IBaseSetting<boolean>;
|
||||
"videoInputMuted": IBaseSetting<boolean>;
|
||||
"activeCallRoomIds": IBaseSetting<string[]>;
|
||||
"releaseAnnouncementData": IBaseSetting<ReleaseAnnouncementData>;
|
||||
"Electron.autoLaunch": IBaseSetting<boolean>;
|
||||
"Electron.warnBeforeExit": IBaseSetting<boolean>;
|
||||
"Electron.alwaysShowMenuBar": IBaseSetting<boolean>;
|
||||
"Electron.showTrayIcon": IBaseSetting<boolean>;
|
||||
"Electron.enableHardwareAcceleration": IBaseSetting<boolean>;
|
||||
}
|
||||
|
||||
export type SettingKey = keyof Settings;
|
||||
export type FeatureSettingKey = Assignable<Settings, IFeature>;
|
||||
export type BooleanSettingKey = Assignable<Settings, IBaseSetting<boolean>> | FeatureSettingKey;
|
||||
|
||||
export const SETTINGS: Settings = {
|
||||
"feature_video_rooms": {
|
||||
isFeature: true,
|
||||
labsGroup: LabGroup.VoiceAndVideo,
|
||||
|
@ -710,7 +881,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
|||
},
|
||||
"custom_themes": {
|
||||
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||
default: [] as CustomTheme[],
|
||||
default: [],
|
||||
},
|
||||
"use_system_theme": {
|
||||
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
|
||||
|
|
|
@ -20,7 +20,7 @@ import RoomSettingsHandler from "./handlers/RoomSettingsHandler";
|
|||
import ConfigSettingsHandler from "./handlers/ConfigSettingsHandler";
|
||||
import { _t } from "../languageHandler";
|
||||
import dis from "../dispatcher/dispatcher";
|
||||
import { IFeature, ISetting, LabGroup, SETTINGS, defaultWatchManager } from "./Settings";
|
||||
import { IFeature, ISetting, LabGroup, SETTINGS, defaultWatchManager, SettingKey, Settings } from "./Settings";
|
||||
import LocalEchoWrapper from "./handlers/LocalEchoWrapper";
|
||||
import { CallbackFn as WatchCallbackFn } from "./WatchManager";
|
||||
import { SettingLevel } from "./SettingLevel";
|
||||
|
@ -34,11 +34,11 @@ import { MatrixClientPeg } from "../MatrixClientPeg";
|
|||
// Convert the settings to easier to manage objects for the handlers
|
||||
const defaultSettings: Record<string, any> = {};
|
||||
const invertedDefaultSettings: Record<string, boolean> = {};
|
||||
const featureNames: string[] = [];
|
||||
const featureNames: SettingKey[] = [];
|
||||
for (const key in SETTINGS) {
|
||||
const setting = SETTINGS[key];
|
||||
const setting = SETTINGS[key as SettingKey];
|
||||
defaultSettings[key] = setting.default;
|
||||
if (setting.isFeature) featureNames.push(key);
|
||||
if (setting.isFeature) featureNames.push(key as SettingKey);
|
||||
if (setting.invertedSettingName) {
|
||||
// Invert now so that the rest of the system will invert it back to what was intended.
|
||||
invertedDefaultSettings[setting.invertedSettingName] = !setting.default;
|
||||
|
@ -80,7 +80,7 @@ function getLevelOrder(setting: ISetting): SettingLevel[] {
|
|||
}
|
||||
|
||||
export type CallbackFn = (
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
atLevel: SettingLevel,
|
||||
newValAtLevel: any,
|
||||
|
@ -138,8 +138,8 @@ export default class SettingsStore {
|
|||
* Gets all the feature-style setting names.
|
||||
* @returns {string[]} The names of the feature settings.
|
||||
*/
|
||||
public static getFeatureSettingNames(): string[] {
|
||||
return Object.keys(SETTINGS).filter((n) => SettingsStore.isFeature(n));
|
||||
public static getFeatureSettingNames(): SettingKey[] {
|
||||
return (Object.keys(SETTINGS) as SettingKey[]).filter((n) => SettingsStore.isFeature(n));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,33 +158,30 @@ export default class SettingsStore {
|
|||
* if the change in value is worthwhile enough to react upon.
|
||||
* @returns {string} A reference to the watcher that was employed.
|
||||
*/
|
||||
public static watchSetting(settingName: string, roomId: string | null, callbackFn: CallbackFn): string {
|
||||
public static watchSetting(settingName: SettingKey, roomId: string | null, callbackFn: CallbackFn): string {
|
||||
const setting = SETTINGS[settingName];
|
||||
const originalSettingName = settingName;
|
||||
if (!setting) throw new Error(`${settingName} is not a setting`);
|
||||
|
||||
if (setting.invertedSettingName) {
|
||||
settingName = setting.invertedSettingName;
|
||||
}
|
||||
const finalSettingName: string = setting.invertedSettingName ?? settingName;
|
||||
|
||||
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${settingName}_${roomId}`;
|
||||
const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${finalSettingName}_${roomId}`;
|
||||
|
||||
const localizedCallback = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any): void => {
|
||||
if (!SettingsStore.doesSettingSupportLevel(originalSettingName, atLevel)) {
|
||||
if (!SettingsStore.doesSettingSupportLevel(settingName, atLevel)) {
|
||||
logger.warn(
|
||||
`Setting handler notified for an update of an invalid setting level: ` +
|
||||
`${originalSettingName}@${atLevel} - this likely means a weird setting value ` +
|
||||
`${settingName}@${atLevel} - this likely means a weird setting value ` +
|
||||
`made it into the level's storage. The notification will be ignored.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const newValue = SettingsStore.getValue(originalSettingName);
|
||||
const newValueAtLevel = SettingsStore.getValueAt(atLevel, originalSettingName) ?? newValAtLevel;
|
||||
callbackFn(originalSettingName, changedInRoomId, atLevel, newValueAtLevel, newValue);
|
||||
const newValue = SettingsStore.getValue(settingName);
|
||||
const newValueAtLevel = SettingsStore.getValueAt(atLevel, settingName) ?? newValAtLevel;
|
||||
callbackFn(settingName, changedInRoomId, atLevel, newValueAtLevel, newValue);
|
||||
};
|
||||
|
||||
SettingsStore.watchers.set(watcherId, localizedCallback);
|
||||
defaultWatchManager.watchSetting(settingName, roomId, localizedCallback);
|
||||
defaultWatchManager.watchSetting(finalSettingName, roomId, localizedCallback);
|
||||
|
||||
return watcherId;
|
||||
}
|
||||
|
@ -214,7 +211,7 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting name to monitor.
|
||||
* @param {String} roomId The room ID to monitor for changes in. Use null for all rooms.
|
||||
*/
|
||||
public static monitorSetting(settingName: string, roomId: string | null): void {
|
||||
public static monitorSetting(settingName: SettingKey, roomId: string | null): void {
|
||||
roomId = roomId || null; // the thing wants null specifically to work, so appease it.
|
||||
|
||||
if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map());
|
||||
|
@ -262,7 +259,7 @@ export default class SettingsStore {
|
|||
* The level to get the display name for; Defaults to 'default'.
|
||||
* @return {String} The display name for the setting, or null if not found.
|
||||
*/
|
||||
public static getDisplayName(settingName: string, atLevel = SettingLevel.DEFAULT): string | null {
|
||||
public static getDisplayName(settingName: SettingKey, atLevel = SettingLevel.DEFAULT): string | null {
|
||||
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
|
||||
|
||||
const displayName = SETTINGS[settingName].displayName;
|
||||
|
@ -285,7 +282,7 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting to look up.
|
||||
* @return {String} The description for the setting, or null if not found.
|
||||
*/
|
||||
public static getDescription(settingName: string): string | ReactNode {
|
||||
public static getDescription(settingName: SettingKey): string | ReactNode {
|
||||
const description = SETTINGS[settingName]?.description;
|
||||
if (!description) return null;
|
||||
if (typeof description !== "string") return description();
|
||||
|
@ -297,7 +294,7 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting to look up.
|
||||
* @return {boolean} True if the setting is a feature.
|
||||
*/
|
||||
public static isFeature(settingName: string): boolean {
|
||||
public static isFeature(settingName: SettingKey): boolean {
|
||||
if (!SETTINGS[settingName]) return false;
|
||||
return !!SETTINGS[settingName].isFeature;
|
||||
}
|
||||
|
@ -307,12 +304,12 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting to look up.
|
||||
* @return {boolean} True if the setting should have a warning sign.
|
||||
*/
|
||||
public static shouldHaveWarning(settingName: string): boolean {
|
||||
public static shouldHaveWarning(settingName: SettingKey): boolean {
|
||||
if (!SETTINGS[settingName]) return false;
|
||||
return SETTINGS[settingName].shouldWarn ?? false;
|
||||
}
|
||||
|
||||
public static getBetaInfo(settingName: string): ISetting["betaInfo"] {
|
||||
public static getBetaInfo(settingName: SettingKey): ISetting["betaInfo"] {
|
||||
// consider a beta disabled if the config is explicitly set to false, in which case treat as normal Labs flag
|
||||
if (
|
||||
SettingsStore.isFeature(settingName) &&
|
||||
|
@ -327,7 +324,7 @@ export default class SettingsStore {
|
|||
}
|
||||
}
|
||||
|
||||
public static getLabGroup(settingName: string): LabGroup | undefined {
|
||||
public static getLabGroup(settingName: SettingKey): LabGroup | undefined {
|
||||
if (SettingsStore.isFeature(settingName)) {
|
||||
return (<IFeature>SETTINGS[settingName]).labsGroup;
|
||||
}
|
||||
|
@ -340,7 +337,7 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting to look up.
|
||||
* @return {string} The reason the setting is disabled.
|
||||
*/
|
||||
public static disabledMessage(settingName: string): string | undefined {
|
||||
public static disabledMessage(settingName: SettingKey): string | undefined {
|
||||
const disabled = SETTINGS[settingName].controller?.settingDisabled;
|
||||
return typeof disabled === "string" ? disabled : undefined;
|
||||
}
|
||||
|
@ -353,7 +350,21 @@ export default class SettingsStore {
|
|||
* @param {boolean} excludeDefault True to disable using the default value.
|
||||
* @return {*} The value, or null if not found
|
||||
*/
|
||||
public static getValue<T = any>(settingName: string, roomId: string | null = null, excludeDefault = false): T {
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null,
|
||||
excludeDefault: true,
|
||||
): Settings[S]["default"] | undefined;
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId?: string | null,
|
||||
excludeDefault?: false,
|
||||
): Settings[S]["default"];
|
||||
public static getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
excludeDefault = false,
|
||||
): Settings[S]["default"] | undefined {
|
||||
// Verify that the setting is actually a setting
|
||||
if (!SETTINGS[settingName]) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
|
@ -362,7 +373,7 @@ export default class SettingsStore {
|
|||
const setting = SETTINGS[settingName];
|
||||
const levelOrder = getLevelOrder(setting);
|
||||
|
||||
return SettingsStore.getValueAt<T>(levelOrder[0], settingName, roomId, false, excludeDefault);
|
||||
return SettingsStore.getValueAt(levelOrder[0], settingName, roomId, false, excludeDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -376,13 +387,13 @@ export default class SettingsStore {
|
|||
* @param {boolean} excludeDefault True to disable using the default value.
|
||||
* @return {*} The value, or null if not found.
|
||||
*/
|
||||
public static getValueAt<T = any>(
|
||||
public static getValueAt<S extends SettingKey>(
|
||||
level: SettingLevel,
|
||||
settingName: string,
|
||||
settingName: S,
|
||||
roomId: string | null = null,
|
||||
explicit = false,
|
||||
excludeDefault = false,
|
||||
): T {
|
||||
): Settings[S]["default"] {
|
||||
// Verify that the setting is actually a setting
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
|
@ -399,9 +410,10 @@ export default class SettingsStore {
|
|||
|
||||
// Check if we need to invert the setting at all. Do this after we get the setting
|
||||
// handlers though, otherwise we'll fail to read the value.
|
||||
let finalSettingName: string = settingName;
|
||||
if (setting.invertedSettingName) {
|
||||
//console.warn(`Inverting ${settingName} to be ${setting.invertedSettingName} - legacy setting`);
|
||||
settingName = setting.invertedSettingName;
|
||||
finalSettingName = setting.invertedSettingName;
|
||||
}
|
||||
|
||||
if (explicit) {
|
||||
|
@ -409,7 +421,7 @@ export default class SettingsStore {
|
|||
if (!handler) {
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, null, null);
|
||||
}
|
||||
const value = handler.getValue(settingName, roomId);
|
||||
const value = handler.getValue(finalSettingName, roomId);
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, value, level);
|
||||
}
|
||||
|
||||
|
@ -418,7 +430,7 @@ export default class SettingsStore {
|
|||
if (!handler) continue;
|
||||
if (excludeDefault && levelOrder[i] === "default") continue;
|
||||
|
||||
const value = handler.getValue(settingName, roomId);
|
||||
const value = handler.getValue(finalSettingName, roomId);
|
||||
if (value === null || value === undefined) continue;
|
||||
return SettingsStore.getFinalValue(setting, level, roomId, value, levelOrder[i]);
|
||||
}
|
||||
|
@ -432,7 +444,7 @@ export default class SettingsStore {
|
|||
* @param {String} roomId The room ID to read the setting value in, may be null.
|
||||
* @return {*} The default value
|
||||
*/
|
||||
public static getDefaultValue(settingName: string): any {
|
||||
public static getDefaultValue(settingName: SettingKey): any {
|
||||
// Verify that the setting is actually a setting
|
||||
if (!SETTINGS[settingName]) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
|
@ -474,7 +486,7 @@ export default class SettingsStore {
|
|||
|
||||
/* eslint-enable valid-jsdoc */
|
||||
public static async setValue(
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
value: any,
|
||||
|
@ -490,24 +502,25 @@ export default class SettingsStore {
|
|||
throw new Error("Setting " + settingName + " does not have a handler for " + level);
|
||||
}
|
||||
|
||||
let finalSettingName: string = settingName;
|
||||
if (setting.invertedSettingName) {
|
||||
// Note: We can't do this when the `level` is "default", however we also
|
||||
// know that the user can't possible change the default value through this
|
||||
// function so we don't bother checking it.
|
||||
//console.warn(`Inverting ${settingName} to be ${setting.invertedSettingName} - legacy setting`);
|
||||
settingName = setting.invertedSettingName;
|
||||
finalSettingName = setting.invertedSettingName;
|
||||
value = !value;
|
||||
}
|
||||
|
||||
if (!handler.canSetValue(settingName, roomId)) {
|
||||
throw new Error("User cannot set " + settingName + " at " + level + " in " + roomId);
|
||||
if (!handler.canSetValue(finalSettingName, roomId)) {
|
||||
throw new Error("User cannot set " + finalSettingName + " at " + level + " in " + roomId);
|
||||
}
|
||||
|
||||
if (setting.controller && !(await setting.controller.beforeChange(level, roomId, value))) {
|
||||
return; // controller says no
|
||||
}
|
||||
|
||||
await handler.setValue(settingName, roomId, value);
|
||||
await handler.setValue(finalSettingName, roomId, value);
|
||||
|
||||
setting.controller?.onChange(level, roomId, value);
|
||||
}
|
||||
|
@ -530,7 +543,7 @@ export default class SettingsStore {
|
|||
* @param {SettingLevel} level The level to check at.
|
||||
* @return {boolean} True if the user may set the setting, false otherwise.
|
||||
*/
|
||||
public static canSetValue(settingName: string, roomId: string | null, level: SettingLevel): boolean {
|
||||
public static canSetValue(settingName: SettingKey, roomId: string | null, level: SettingLevel): boolean {
|
||||
const setting = SETTINGS[settingName];
|
||||
// Verify that the setting is actually a setting
|
||||
if (!setting) {
|
||||
|
@ -563,7 +576,7 @@ export default class SettingsStore {
|
|||
* @returns
|
||||
*/
|
||||
public static settingIsOveriddenAtConfigLevel(
|
||||
settingName: string,
|
||||
settingName: SettingKey,
|
||||
roomId: string | null,
|
||||
level: SettingLevel,
|
||||
): boolean {
|
||||
|
@ -597,7 +610,7 @@ export default class SettingsStore {
|
|||
* the level itself can be supported by the runtime (ie: you will need to call #isLevelSupported()
|
||||
* on your own).
|
||||
*/
|
||||
public static doesSettingSupportLevel(settingName: string, level: SettingLevel): boolean {
|
||||
public static doesSettingSupportLevel(settingName: SettingKey, level: SettingLevel): boolean {
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
throw new Error("Setting '" + settingName + "' does not appear to be a setting.");
|
||||
|
@ -612,7 +625,7 @@ export default class SettingsStore {
|
|||
* @param {string} settingName The setting name.
|
||||
* @return {SettingLevel}
|
||||
*/
|
||||
public static firstSupportedLevel(settingName: string): SettingLevel | null {
|
||||
public static firstSupportedLevel(settingName: SettingKey): SettingLevel | null {
|
||||
// Verify that the setting is actually a setting
|
||||
const setting = SETTINGS[settingName];
|
||||
if (!setting) {
|
||||
|
@ -699,7 +712,7 @@ export default class SettingsStore {
|
|||
* @param {string} realSettingName The setting name to try and read.
|
||||
* @param {string} roomId Optional room ID to test the setting in.
|
||||
*/
|
||||
public static debugSetting(realSettingName: string, roomId: string): void {
|
||||
public static debugSetting(realSettingName: SettingKey, roomId: string): void {
|
||||
logger.log(`--- DEBUG ${realSettingName}`);
|
||||
|
||||
// Note: we intentionally use JSON.stringify here to avoid the console masking the
|
||||
|
@ -711,7 +724,7 @@ export default class SettingsStore {
|
|||
logger.log(`--- default level order: ${JSON.stringify(LEVEL_ORDER)}`);
|
||||
logger.log(`--- registered handlers: ${JSON.stringify(Object.keys(LEVEL_HANDLERS))}`);
|
||||
|
||||
const doChecks = (settingName: string): void => {
|
||||
const doChecks = (settingName: SettingKey): void => {
|
||||
for (const handlerName of Object.keys(LEVEL_HANDLERS)) {
|
||||
const handler = LEVEL_HANDLERS[handlerName as SettingLevel];
|
||||
|
||||
|
@ -803,19 +816,19 @@ export default class SettingsStore {
|
|||
if (def.invertedSettingName) {
|
||||
logger.log(`--- TESTING INVERTED SETTING NAME`);
|
||||
logger.log(`--- inverted: ${def.invertedSettingName}`);
|
||||
doChecks(def.invertedSettingName);
|
||||
doChecks(def.invertedSettingName as SettingKey);
|
||||
}
|
||||
|
||||
logger.log(`--- END DEBUG`);
|
||||
}
|
||||
|
||||
private static getHandler(settingName: string, level: SettingLevel): SettingsHandler | null {
|
||||
private static getHandler(settingName: SettingKey, level: SettingLevel): SettingsHandler | null {
|
||||
const handlers = SettingsStore.getHandlers(settingName);
|
||||
if (!handlers[level]) return null;
|
||||
return handlers[level]!;
|
||||
}
|
||||
|
||||
private static getHandlers(settingName: string): HandlerMap {
|
||||
private static getHandlers(settingName: SettingKey): HandlerMap {
|
||||
if (!SETTINGS[settingName]) return {};
|
||||
|
||||
const handlers: Partial<Record<SettingLevel, SettingsHandler>> = {};
|
||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import SettingController from "./SettingController";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { BooleanSettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Enforces that a boolean setting cannot be enabled if the incompatible setting
|
||||
|
@ -17,7 +18,7 @@ import SettingsStore from "../SettingsStore";
|
|||
*/
|
||||
export default class IncompatibleController extends SettingController {
|
||||
public constructor(
|
||||
private settingName: string,
|
||||
private settingName: BooleanSettingKey,
|
||||
private forcedValue: any = false,
|
||||
private incompatibleValue: any | ((v: any) => boolean) = true,
|
||||
) {
|
||||
|
|
|
@ -10,6 +10,7 @@ import { SettingLevel } from "../SettingLevel";
|
|||
import MatrixClientBackedController from "./MatrixClientBackedController";
|
||||
import { WatchManager } from "../WatchManager";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { SettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Disables a given setting if the server unstable feature it requires is not supported
|
||||
|
@ -28,7 +29,7 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient
|
|||
* the features in the group are supported (all features in a group are required).
|
||||
*/
|
||||
public constructor(
|
||||
private readonly settingName: string,
|
||||
private readonly settingName: SettingKey,
|
||||
private readonly watchers: WatchManager,
|
||||
private readonly unstableFeatureGroups: string[][],
|
||||
private readonly stableVersion?: string,
|
||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import SettingController from "./SettingController";
|
||||
import { SettingLevel } from "../SettingLevel";
|
||||
import SettingsStore from "../SettingsStore";
|
||||
import { SettingKey } from "../Settings.tsx";
|
||||
|
||||
/**
|
||||
* Enforces that a boolean setting cannot be enabled if the corresponding
|
||||
|
@ -19,7 +20,7 @@ import SettingsStore from "../SettingsStore";
|
|||
*/
|
||||
export default class UIFeatureController extends SettingController {
|
||||
public constructor(
|
||||
private uiFeatureName: string,
|
||||
private uiFeatureName: SettingKey,
|
||||
private forcedValue = false,
|
||||
) {
|
||||
super();
|
||||
|
|
|
@ -57,7 +57,7 @@ export class FontWatcher implements IWatcher {
|
|||
* @private
|
||||
*/
|
||||
private async migrateBaseFontV1toFontSizeDelta(): Promise<void> {
|
||||
const legacyBaseFontSize = SettingsStore.getValue<number>("baseFontSize");
|
||||
const legacyBaseFontSize = SettingsStore.getValue("baseFontSize");
|
||||
// No baseFontV1 found, nothing to migrate
|
||||
if (!legacyBaseFontSize) return;
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class FontWatcher implements IWatcher {
|
|||
* @private
|
||||
*/
|
||||
private async migrateBaseFontV2toFontSizeDelta(): Promise<void> {
|
||||
const legacyBaseFontV2Size = SettingsStore.getValue<number>("baseFontSizeV2");
|
||||
const legacyBaseFontV2Size = SettingsStore.getValue("baseFontSizeV2");
|
||||
// No baseFontV2 found, nothing to migrate
|
||||
if (!legacyBaseFontV2Size) return;
|
||||
|
||||
|
@ -140,7 +140,7 @@ export class FontWatcher implements IWatcher {
|
|||
* @returns {number} the default font size of the browser
|
||||
*/
|
||||
public static getBrowserDefaultFontSize(): number {
|
||||
return this.getRootFontSize() - SettingsStore.getValue<number>("fontSizeDelta");
|
||||
return this.getRootFontSize() - SettingsStore.getValue("fontSizeDelta");
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
|
@ -148,7 +148,7 @@ export class FontWatcher implements IWatcher {
|
|||
}
|
||||
|
||||
private updateFont(): void {
|
||||
this.setRootFontSize(SettingsStore.getValue<number>("fontSizeDelta"));
|
||||
this.setRootFontSize(SettingsStore.getValue("fontSizeDelta"));
|
||||
this.setSystemFont({
|
||||
useBundledEmojiFont: SettingsStore.getValue("useBundledEmojiFont"),
|
||||
useSystemFont: SettingsStore.getValue("useSystemFont"),
|
||||
|
|
|
@ -11,6 +11,7 @@ import { KnownMembership } from "matrix-js-sdk/src/types";
|
|||
|
||||
import SettingsStore from "./settings/SettingsStore";
|
||||
import { IRoomState } from "./components/structures/RoomView";
|
||||
import { SettingKey } from "./settings/Settings.tsx";
|
||||
|
||||
interface IDiff {
|
||||
isMemberEvent: boolean;
|
||||
|
@ -53,7 +54,7 @@ export default function shouldHideEvent(ev: MatrixEvent, ctx?: IRoomState): bool
|
|||
// so we should prefer using cached values if a RoomContext is available
|
||||
const isEnabled = ctx
|
||||
? (name: keyof IRoomState) => ctx[name]
|
||||
: (name: string) => SettingsStore.getValue(name, ev.getRoomId());
|
||||
: (name: SettingKey) => SettingsStore.getValue(name, ev.getRoomId());
|
||||
|
||||
// Hide redacted events
|
||||
// Deleted events with a thread are always shown regardless of user preference
|
||||
|
|
|
@ -127,7 +127,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
|
|||
};
|
||||
|
||||
private async updateRooms(): Promise<void> {
|
||||
let roomIds = SettingsStore.getValue<string[]>("breadcrumb_rooms");
|
||||
let roomIds = SettingsStore.getValue("breadcrumb_rooms");
|
||||
if (!roomIds || roomIds.length === 0) roomIds = [];
|
||||
|
||||
const rooms = filterBoolean(roomIds.map((r) => this.matrixClient?.getRoom(r)));
|
||||
|
|
|
@ -61,7 +61,7 @@ export class CallStore extends AsyncStoreWithClient<{}> {
|
|||
// If the room ID of a previously connected call is still in settings at
|
||||
// this time, that's a sign that we failed to disconnect from it
|
||||
// properly, and need to clean up after ourselves
|
||||
const uncleanlyDisconnectedRoomIds = SettingsStore.getValue<string[]>("activeCallRoomIds");
|
||||
const uncleanlyDisconnectedRoomIds = SettingsStore.getValue("activeCallRoomIds");
|
||||
if (uncleanlyDisconnectedRoomIds.length) {
|
||||
await Promise.all([
|
||||
...uncleanlyDisconnectedRoomIds.map(async (uncleanlyDisconnectedRoomId): Promise<void> => {
|
||||
|
|
|
@ -26,7 +26,9 @@ export type Feature = (typeof FEATURES)[number];
|
|||
* The stored settings for the release announcements.
|
||||
* The boolean is at true when the user has viewed the feature
|
||||
*/
|
||||
type StoredSettings = Record<Feature, boolean>;
|
||||
type StoredSettings = Partial<Record<Feature, boolean>>;
|
||||
|
||||
export type ReleaseAnnouncementData = StoredSettings;
|
||||
|
||||
/**
|
||||
* The events emitted by the ReleaseAnnouncementStore.
|
||||
|
@ -82,7 +84,7 @@ export class ReleaseAnnouncementStore extends TypedEventEmitter<ReleaseAnnouncem
|
|||
*/
|
||||
private getViewedReleaseAnnouncements(): StoredSettings {
|
||||
// Clone the settings to avoid to mutate the internal stored value in the SettingsStore
|
||||
return cloneDeep(SettingsStore.getValue<StoredSettings>("releaseAnnouncementData"));
|
||||
return cloneDeep(SettingsStore.getValue("releaseAnnouncementData"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,7 +92,7 @@ export class ReleaseAnnouncementStore extends TypedEventEmitter<ReleaseAnnouncem
|
|||
* @private
|
||||
*/
|
||||
private isReleaseAnnouncementEnabled(): boolean {
|
||||
return SettingsStore.getValue<boolean>(Features.ReleaseAnnouncement);
|
||||
return SettingsStore.getValue(Features.ReleaseAnnouncement);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -252,10 +252,13 @@ export default class RightPanelStore extends ReadyWatchingStore {
|
|||
const room = this.mxClient?.getRoom(this.viewedRoomId);
|
||||
if (!!room) {
|
||||
this.global =
|
||||
this.global ?? convertToStatePanel(SettingsStore.getValue("RightPanel.phasesGlobal"), room);
|
||||
this.global ??
|
||||
convertToStatePanel(SettingsStore.getValue("RightPanel.phasesGlobal"), room) ??
|
||||
undefined;
|
||||
this.byRoom[this.viewedRoomId] =
|
||||
this.byRoom[this.viewedRoomId] ??
|
||||
convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room);
|
||||
convertToStatePanel(SettingsStore.getValue("RightPanel.phases", this.viewedRoomId), room) ??
|
||||
undefined;
|
||||
} else {
|
||||
logger.warn(
|
||||
"Could not restore the right panel after load because there was no associated room object.",
|
||||
|
|
|
@ -57,10 +57,10 @@ export interface IRightPanelForRoom {
|
|||
history: Array<IRightPanelCard>;
|
||||
}
|
||||
|
||||
interface IRightPanelForRoomStored {
|
||||
export type IRightPanelForRoomStored = {
|
||||
isOpen: boolean;
|
||||
history: Array<IRightPanelCardStored>;
|
||||
}
|
||||
};
|
||||
|
||||
export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | undefined {
|
||||
if (!cacheRoom) return undefined;
|
||||
|
@ -68,7 +68,7 @@ export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanel
|
|||
return { isOpen: cacheRoom.isOpen, history: storeHistory };
|
||||
}
|
||||
|
||||
export function convertToStatePanel(storeRoom: IRightPanelForRoomStored, room: Room): IRightPanelForRoom {
|
||||
export function convertToStatePanel(storeRoom: IRightPanelForRoomStored | null, room: Room): IRightPanelForRoom | null {
|
||||
if (!storeRoom) return storeRoom;
|
||||
const stateHistory = [...storeRoom.history].map((panelStateStore) => convertStoreToCard(panelStateStore, room));
|
||||
return { history: stateHistory, isOpen: storeRoom.isOpen };
|
||||
|
|
|
@ -239,7 +239,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
if (!isMetaSpace(space)) {
|
||||
cliSpace = this.matrixClient.getRoom(space);
|
||||
if (!cliSpace?.isSpaceRoom()) return;
|
||||
} else if (!this.enabledMetaSpaces.includes(space as MetaSpace)) {
|
||||
} else if (!this.enabledMetaSpaces.includes(space)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1178,7 +1178,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
|||
}
|
||||
|
||||
// restore selected state from last session if any and still valid
|
||||
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY);
|
||||
const lastSpaceId = window.localStorage.getItem(ACTIVE_SPACE_LS_KEY) as MetaSpace;
|
||||
const valid =
|
||||
lastSpaceId &&
|
||||
(!isMetaSpace(lastSpaceId) ? this.matrixClient.getRoom(lastSpaceId) : enabledMetaSpaces[lastSpaceId]);
|
||||
|
|
|
@ -48,7 +48,7 @@ export interface ISuggestedRoom extends HierarchyRoom {
|
|||
viaServers: string[];
|
||||
}
|
||||
|
||||
export function isMetaSpace(spaceKey?: SpaceKey): boolean {
|
||||
export function isMetaSpace(spaceKey?: SpaceKey): spaceKey is MetaSpace {
|
||||
return (
|
||||
spaceKey === MetaSpace.Home ||
|
||||
spaceKey === MetaSpace.Favourites ||
|
||||
|
|
|
@ -25,9 +25,9 @@ import { Container, IStoredLayout, ILayoutStateEvent, WIDGET_LAYOUT_EVENT_TYPE,
|
|||
export type { IStoredLayout, ILayoutStateEvent };
|
||||
export { Container, WIDGET_LAYOUT_EVENT_TYPE };
|
||||
|
||||
interface ILayoutSettings extends ILayoutStateEvent {
|
||||
export type ILayoutSettings = Partial<ILayoutStateEvent> & {
|
||||
overrides?: string; // event ID for layout state event, if present
|
||||
}
|
||||
};
|
||||
|
||||
// Dev note: "Pinned" widgets are ones in the top container.
|
||||
export const MAX_PINNED = 3;
|
||||
|
@ -149,7 +149,7 @@ export class WidgetLayoutStore extends ReadyWatchingStore {
|
|||
|
||||
const layoutEv = room.currentState.getStateEvents(WIDGET_LAYOUT_EVENT_TYPE, "");
|
||||
const legacyPinned = SettingsStore.getValue("Widgets.pinned", room.roomId);
|
||||
let userLayout = SettingsStore.getValue<ILayoutSettings | null>("Widgets.layout", room.roomId);
|
||||
let userLayout = SettingsStore.getValue("Widgets.layout", room.roomId);
|
||||
|
||||
if (layoutEv && userLayout && userLayout.overrides !== layoutEv.getId()) {
|
||||
// For some other layout that we don't really care about. The user can reset this
|
||||
|
|
|
@ -53,10 +53,7 @@ export class WidgetPermissionStore {
|
|||
public setOIDCState(widget: Widget, kind: WidgetKind, roomId: string | undefined, newState: OIDCState): void {
|
||||
const settingsKey = this.packSettingKey(widget, kind, roomId);
|
||||
|
||||
let currentValues = SettingsStore.getValue<{
|
||||
allow?: string[];
|
||||
deny?: string[];
|
||||
}>("widgetOpenIDPermissions");
|
||||
let currentValues = SettingsStore.getValue("widgetOpenIDPermissions");
|
||||
if (!currentValues) {
|
||||
currentValues = {};
|
||||
}
|
||||
|
|
|
@ -263,9 +263,9 @@ export function getCustomTheme(themeName: string): CustomTheme {
|
|||
if (!customThemes) {
|
||||
throw new Error(`No custom themes set, can't set custom theme "${themeName}"`);
|
||||
}
|
||||
const customTheme = customThemes.find((t: ITheme) => t.name === themeName);
|
||||
const customTheme = customThemes.find((t: CustomTheme) => t.name === themeName);
|
||||
if (!customTheme) {
|
||||
const knownNames = customThemes.map((t: ITheme) => t.name).join(", ");
|
||||
const knownNames = customThemes.map((t: CustomTheme) => t.name).join(", ");
|
||||
throw new Error(`Can't find custom theme "${themeName}", only know ${knownNames}`);
|
||||
}
|
||||
return customTheme;
|
||||
|
|
|
@ -20,6 +20,7 @@ import { IndicatorIcon } from "@vector-im/compound-web";
|
|||
import SettingsStore from "../settings/SettingsStore";
|
||||
import { NotificationLevel } from "../stores/notifications/NotificationLevel";
|
||||
import { doesRoomHaveUnreadMessages } from "../Unread";
|
||||
import { SettingKey } from "../settings/Settings.tsx";
|
||||
|
||||
// MSC2867 is not yet spec at time of writing. We read from both stable
|
||||
// and unstable prefixes and accept the risk that the format may change,
|
||||
|
@ -34,7 +35,7 @@ export const MARKED_UNREAD_TYPE_UNSTABLE = "com.famedly.marked_unread";
|
|||
*/
|
||||
export const MARKED_UNREAD_TYPE_STABLE = "m.marked_unread";
|
||||
|
||||
export const deviceNotificationSettingsKeys = [
|
||||
export const deviceNotificationSettingsKeys: SettingKey[] = [
|
||||
"notificationsEnabled",
|
||||
"notificationBodyEnabled",
|
||||
"audioNotificationsEnabled",
|
||||
|
|
|
@ -343,7 +343,7 @@ describe("Notifier", () => {
|
|||
|
||||
describe("getSoundForRoom", () => {
|
||||
it("should not explode if given invalid url", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
return { url: { content_uri: "foobar" } };
|
||||
});
|
||||
expect(Notifier.getSoundForRoom("!roomId:server")).toBeNull();
|
||||
|
|
|
@ -309,7 +309,7 @@ describe("SlidingSyncManager", () => {
|
|||
});
|
||||
it("uses the legacy `feature_sliding_sync_proxy_url` if it was set", async () => {
|
||||
jest.spyOn(manager, "getProxyFromWellKnown").mockResolvedValue("https://proxy/");
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "feature_sliding_sync_proxy_url") return "legacy-proxy";
|
||||
});
|
||||
await manager.setup(client);
|
||||
|
|
|
@ -1493,7 +1493,7 @@ describe("<MatrixChat />", () => {
|
|||
};
|
||||
|
||||
const enabledMobileRegistration = (): void => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any => {
|
||||
if (settingName === "Registration.mobileRegistrationHelper") return true;
|
||||
if (settingName === UIFeature.Registration) return true;
|
||||
});
|
||||
|
|
|
@ -303,8 +303,8 @@ describe("TimelinePanel", () => {
|
|||
client.isVersionSupported.mockResolvedValue(true);
|
||||
client.doesServerSupportUnstableFeature.mockResolvedValue(true);
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting: string) => {
|
||||
if (setting === "sendReadReceipt") return false;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting: string): any => {
|
||||
if (setting === "sendReadReceipts") return false;
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
|
|
@ -14,13 +14,14 @@ import { shouldShowFeedback } from "../../../../../src/utils/Feedback";
|
|||
import BetaCard from "../../../../../src/components/views/beta/BetaCard";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { TranslationKey } from "../../../../../src/languageHandler";
|
||||
import { FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
|
||||
|
||||
jest.mock("../../../../../src/utils/Feedback");
|
||||
jest.mock("../../../../../src/settings/SettingsStore");
|
||||
|
||||
describe("<BetaCard />", () => {
|
||||
describe("Feedback prompt", () => {
|
||||
const featureId = "featureId";
|
||||
const featureId = "featureId" as FeatureSettingKey;
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(SettingsStore).getBetaInfo.mockReturnValue({
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { FeatureSettingKey } from "../../../../../src/settings/Settings.tsx";
|
||||
|
||||
mockPlatformPeg({
|
||||
supportsSpellCheckSettings: jest.fn().mockReturnValue(false),
|
||||
|
@ -111,13 +112,13 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
|
||||
it("renders ignored users tab when feature_mjolnir is enabled", () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === "feature_mjolnir");
|
||||
mockSettingsStore.getValue.mockImplementation((settingName) => settingName === "feature_mjolnir");
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders voip tab when voip is enabled", () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
|
||||
mockSettingsStore.getValue.mockImplementation((settingName: any): any => settingName === UIFeature.Voip);
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
|
||||
});
|
||||
|
@ -165,7 +166,7 @@ describe("<UserSettingsDialog />", () => {
|
|||
|
||||
it("renders with voip tab selected", () => {
|
||||
useMockMediaDevices();
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
|
||||
mockSettingsStore.getValue.mockImplementation((settingName: any): any => settingName === UIFeature.Voip);
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Voice }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Voice & Video");
|
||||
|
@ -212,8 +213,11 @@ describe("<UserSettingsDialog />", () => {
|
|||
});
|
||||
|
||||
it("renders labs tab when some feature is in beta", () => {
|
||||
mockSettingsStore.getFeatureSettingNames.mockReturnValue(["feature_beta_setting", "feature_just_normal_labs"]);
|
||||
mockSettingsStore.getBetaInfo.mockImplementation((settingName) =>
|
||||
mockSettingsStore.getFeatureSettingNames.mockReturnValue([
|
||||
"feature_beta_setting",
|
||||
"feature_just_normal_labs",
|
||||
] as unknown[] as FeatureSettingKey[]);
|
||||
mockSettingsStore.getBetaInfo.mockImplementation((settingName: any) =>
|
||||
settingName === "feature_beta_setting" ? ({} as any) : undefined,
|
||||
);
|
||||
const { getByTestId } = render(getComponent());
|
||||
|
|
|
@ -66,11 +66,13 @@ describe("MessageComposer", () => {
|
|||
|
||||
// restore settings
|
||||
act(() => {
|
||||
[
|
||||
"MessageComposerInput.showStickersButton",
|
||||
"MessageComposerInput.showPollsButton",
|
||||
"feature_wysiwyg_composer",
|
||||
].forEach((setting: string): void => {
|
||||
(
|
||||
[
|
||||
"MessageComposerInput.showStickersButton",
|
||||
"MessageComposerInput.showPollsButton",
|
||||
"feature_wysiwyg_composer",
|
||||
] as const
|
||||
).forEach((setting): void => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, SettingsStore.getDefaultValue(setting));
|
||||
});
|
||||
});
|
||||
|
@ -188,11 +190,11 @@ describe("MessageComposer", () => {
|
|||
// test button display depending on settings
|
||||
[
|
||||
{
|
||||
setting: "MessageComposerInput.showStickersButton",
|
||||
setting: "MessageComposerInput.showStickersButton" as const,
|
||||
buttonLabel: "Sticker",
|
||||
},
|
||||
{
|
||||
setting: "MessageComposerInput.showPollsButton",
|
||||
setting: "MessageComposerInput.showPollsButton" as const,
|
||||
buttonLabel: "Poll",
|
||||
},
|
||||
].forEach(({ setting, buttonLabel }) => {
|
||||
|
|
|
@ -176,7 +176,7 @@ describe("RoomHeader", () => {
|
|||
});
|
||||
|
||||
it("opens the notifications panel", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "feature_notifications") return true;
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ describe("RoomPreviewCard", () => {
|
|||
client.reEmitter.reEmit(room, [RoomStateEvent.Events]);
|
||||
|
||||
enabledFeatures = [];
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) =>
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any =>
|
||||
enabledFeatures.includes(settingName) ? true : undefined,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -419,7 +419,7 @@ describe("WysiwygComposer", () => {
|
|||
const onChange = jest.fn();
|
||||
const onSend = jest.fn();
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "MessageComposerInput.autoReplaceEmoji") return true;
|
||||
});
|
||||
customRender(onChange, onSend);
|
||||
|
@ -455,7 +455,7 @@ describe("WysiwygComposer", () => {
|
|||
const onChange = jest.fn();
|
||||
const onSend = jest.fn();
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name): any => {
|
||||
if (name === "MessageComposerInput.ctrlEnterToSend") return true;
|
||||
});
|
||||
customRender(onChange, onSend);
|
||||
|
|
|
@ -57,7 +57,7 @@ describe("<LayoutSwitcher />", () => {
|
|||
act(() => screen.getByRole("radio", { name: "Message bubbles" }).click());
|
||||
|
||||
expect(screen.getByRole("radio", { name: "Message bubbles" })).toBeChecked();
|
||||
await waitFor(() => expect(SettingsStore.getValue<boolean>("layout")).toBe(Layout.Bubble));
|
||||
await waitFor(() => expect(SettingsStore.getValue("layout")).toBe(Layout.Bubble));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -77,7 +77,7 @@ describe("<LayoutSwitcher />", () => {
|
|||
await renderLayoutSwitcher();
|
||||
act(() => screen.getByRole("checkbox", { name: "Show compact text and messages" }).click());
|
||||
|
||||
await waitFor(() => expect(SettingsStore.getValue<boolean>("useCompactLayout")).toBe(true));
|
||||
await waitFor(() => expect(SettingsStore.getValue("useCompactLayout")).toBe(true));
|
||||
});
|
||||
|
||||
it("should be disabled when the modern layout is not enabled", async () => {
|
||||
|
|
|
@ -40,7 +40,7 @@ describe("DiscoverySettings", () => {
|
|||
const DiscoveryWrapper = (props = {}) => <MatrixClientContext.Provider value={client} {...props} />;
|
||||
|
||||
it("is empty if 3pid features are disabled", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((key) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((key: any): any => {
|
||||
if (key === UIFeature.ThirdPartyID) return false;
|
||||
});
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ describe("RolesRoomSettingsTab", () => {
|
|||
|
||||
describe("Element Call", () => {
|
||||
const setGroupCallsEnabled = (val: boolean): void => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "feature_group_calls") return val;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
|||
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
|
||||
import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
import PlatformPeg from "../../../../../../../src/PlatformPeg";
|
||||
import { SettingKey } from "../../../../../../../src/settings/Settings.tsx";
|
||||
|
||||
describe("PreferencesUserSettingsTab", () => {
|
||||
beforeEach(() => {
|
||||
|
@ -121,13 +122,13 @@ describe("PreferencesUserSettingsTab", () => {
|
|||
const mockGetValue = (val: boolean) => {
|
||||
const copyOfGetValueAt = SettingsStore.getValueAt;
|
||||
|
||||
SettingsStore.getValueAt = <T,>(
|
||||
SettingsStore.getValueAt = (
|
||||
level: SettingLevel,
|
||||
name: string,
|
||||
name: SettingKey,
|
||||
roomId?: string,
|
||||
isExplicit?: boolean,
|
||||
): T => {
|
||||
if (name === "sendReadReceipts") return val as T;
|
||||
) => {
|
||||
if (name === "sendReadReceipts") return val;
|
||||
return copyOfGetValueAt(level, name, roomId, isExplicit);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -19,6 +19,7 @@ import { populateThread } from "../../../../test-utils/threads";
|
|||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import { Features } from "../../../../../src/settings/Settings.tsx";
|
||||
|
||||
describe("ThreadsActivityCentre", () => {
|
||||
const getTACButton = () => {
|
||||
|
@ -92,7 +93,7 @@ describe("ThreadsActivityCentre", () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, false);
|
||||
await SettingsStore.setValue(Features.ReleaseAnnouncement, null, SettingLevel.DEVICE, false);
|
||||
});
|
||||
|
||||
it("should render the threads activity centre button", async () => {
|
||||
|
@ -102,7 +103,7 @@ describe("ThreadsActivityCentre", () => {
|
|||
|
||||
it("should render the release announcement", async () => {
|
||||
// Enable release announcement
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, true);
|
||||
await SettingsStore.setValue(Features.ReleaseAnnouncement, null, SettingLevel.DEVICE, true);
|
||||
|
||||
renderTAC();
|
||||
expect(document.body).toMatchSnapshot();
|
||||
|
@ -110,7 +111,7 @@ describe("ThreadsActivityCentre", () => {
|
|||
|
||||
it("should render not display the tooltip when the release announcement is displayed", async () => {
|
||||
// Enable release announcement
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, true);
|
||||
await SettingsStore.setValue(Features.ReleaseAnnouncement, null, SettingLevel.DEVICE, true);
|
||||
|
||||
renderTAC();
|
||||
|
||||
|
@ -121,7 +122,7 @@ describe("ThreadsActivityCentre", () => {
|
|||
|
||||
it("should close the release announcement when the TAC button is clicked", async () => {
|
||||
// Enable release announcement
|
||||
await SettingsStore.setValue("feature_release_announcement", null, SettingLevel.DEVICE, true);
|
||||
await SettingsStore.setValue(Features.ReleaseAnnouncement, null, SettingLevel.DEVICE, true);
|
||||
|
||||
renderTAC();
|
||||
await userEvent.click(getTACButton());
|
||||
|
|
|
@ -105,7 +105,7 @@ describe("createRoom", () => {
|
|||
});
|
||||
|
||||
it("correctly sets up MSC3401 power levels", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string): any => {
|
||||
if (name === "feature_group_calls") return true;
|
||||
});
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ import ActiveWidgetStore, { ActiveWidgetStoreEvent } from "../../../src/stores/A
|
|||
import { ElementWidgetActions } from "../../../src/stores/widgets/ElementWidgetActions";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { PosthogAnalytics } from "../../../src/PosthogAnalytics";
|
||||
import { SettingKey } from "../../../src/settings/Settings.tsx";
|
||||
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [
|
||||
|
@ -63,7 +64,7 @@ jest.spyOn(MediaDeviceHandler, "getVideoInput").mockReturnValue("2");
|
|||
|
||||
const enabledSettings = new Set(["feature_group_calls", "feature_video_rooms", "feature_element_call_video_rooms"]);
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => enabledSettings.has(settingName) || undefined,
|
||||
(settingName): any => enabledSettings.has(settingName) || undefined,
|
||||
);
|
||||
|
||||
const setUpClientRoomAndStores = (): {
|
||||
|
@ -709,16 +710,18 @@ describe("ElementCall", () => {
|
|||
|
||||
it("passes font settings through widget URL", async () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
SettingsStore.getValue = <T>(name: string, roomId?: string, excludeDefault?: boolean) => {
|
||||
SettingsStore.getValue = (name: SettingKey, roomId: string | null = null, excludeDefault = false): any => {
|
||||
switch (name) {
|
||||
case "fontSizeDelta":
|
||||
return 4 as T;
|
||||
return 4;
|
||||
case "useSystemFont":
|
||||
return true as T;
|
||||
return true;
|
||||
case "systemFont":
|
||||
return "OpenDyslexic, DejaVu Sans" as T;
|
||||
return "OpenDyslexic, DejaVu Sans";
|
||||
default:
|
||||
return originalGetValue<T>(name, roomId, excludeDefault);
|
||||
return excludeDefault
|
||||
? originalGetValue(name, roomId, excludeDefault)
|
||||
: originalGetValue(name, roomId, excludeDefault);
|
||||
}
|
||||
};
|
||||
document.documentElement.style.fontSize = "12px";
|
||||
|
@ -746,12 +749,14 @@ describe("ElementCall", () => {
|
|||
|
||||
// Now test with the preference set to true
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
SettingsStore.getValue = <T>(name: string, roomId?: string, excludeDefault?: boolean) => {
|
||||
SettingsStore.getValue = (name: SettingKey, roomId: string | null = null, excludeDefault = false): any => {
|
||||
switch (name) {
|
||||
case "fallbackICEServerAllowed":
|
||||
return true as T;
|
||||
return true;
|
||||
default:
|
||||
return originalGetValue<T>(name, roomId, excludeDefault);
|
||||
return excludeDefault
|
||||
? originalGetValue(name, roomId, excludeDefault)
|
||||
: originalGetValue(name, roomId, excludeDefault);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -803,12 +808,14 @@ describe("ElementCall", () => {
|
|||
it("passes feature_allow_screen_share_only_mode setting to allowVoipWithNoMedia url param", async () => {
|
||||
// Now test with the preference set to true
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
SettingsStore.getValue = <T>(name: string, roomId?: string, excludeDefault?: boolean) => {
|
||||
SettingsStore.getValue = (name: SettingKey, roomId: string | null = null, excludeDefault = false): any => {
|
||||
switch (name) {
|
||||
case "feature_allow_screen_share_only_mode":
|
||||
return true as T;
|
||||
return true;
|
||||
default:
|
||||
return originalGetValue<T>(name, roomId, excludeDefault);
|
||||
return excludeDefault
|
||||
? originalGetValue(name, roomId, excludeDefault)
|
||||
: originalGetValue(name, roomId, excludeDefault);
|
||||
}
|
||||
};
|
||||
await ElementCall.create(room);
|
||||
|
|
|
@ -13,10 +13,11 @@ import SdkConfig from "../../../src/SdkConfig";
|
|||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { mkStubRoom, mockPlatformPeg, stubClient } from "../../test-utils";
|
||||
import { SettingKey } from "../../../src/settings/Settings.tsx";
|
||||
|
||||
const TEST_DATA = [
|
||||
{
|
||||
name: "Electron.showTrayIcon",
|
||||
name: "Electron.showTrayIcon" as SettingKey,
|
||||
level: SettingLevel.PLATFORM,
|
||||
value: true,
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import IncompatibleController from "../../../../src/settings/controllers/IncompatibleController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { FeatureSettingKey } from "../../../../src/settings/Settings.tsx";
|
||||
|
||||
describe("IncompatibleController", () => {
|
||||
const settingsGetValueSpy = jest.spyOn(SettingsStore, "getValue");
|
||||
|
@ -20,7 +21,7 @@ describe("IncompatibleController", () => {
|
|||
describe("when incompatibleValue is not set", () => {
|
||||
it("returns true when setting value is true", () => {
|
||||
// no incompatible value set, defaulted to true
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null });
|
||||
const controller = new IncompatibleController("feature_spotlight" as FeatureSettingKey, { key: null });
|
||||
settingsGetValueSpy.mockReturnValue(true);
|
||||
// true === true
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
|
@ -30,7 +31,7 @@ describe("IncompatibleController", () => {
|
|||
|
||||
it("returns false when setting value is not true", () => {
|
||||
// no incompatible value set, defaulted to true
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null });
|
||||
const controller = new IncompatibleController("feature_spotlight" as FeatureSettingKey, { key: null });
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
});
|
||||
|
@ -38,13 +39,21 @@ describe("IncompatibleController", () => {
|
|||
|
||||
describe("when incompatibleValue is set to a value", () => {
|
||||
it("returns true when setting value matches incompatible value", () => {
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null }, "test");
|
||||
const controller = new IncompatibleController(
|
||||
"feature_spotlight" as FeatureSettingKey,
|
||||
{ key: null },
|
||||
"test",
|
||||
);
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when setting value is not true", () => {
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null }, "test");
|
||||
const controller = new IncompatibleController(
|
||||
"feature_spotlight" as FeatureSettingKey,
|
||||
{ key: null },
|
||||
"test",
|
||||
);
|
||||
settingsGetValueSpy.mockReturnValue("not test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
});
|
||||
|
@ -53,7 +62,11 @@ describe("IncompatibleController", () => {
|
|||
describe("when incompatibleValue is set to a function", () => {
|
||||
it("returns result from incompatibleValue function", () => {
|
||||
const incompatibleValueFn = jest.fn().mockReturnValue(false);
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null }, incompatibleValueFn);
|
||||
const controller = new IncompatibleController(
|
||||
"feature_spotlight" as FeatureSettingKey,
|
||||
{ key: null },
|
||||
incompatibleValueFn,
|
||||
);
|
||||
settingsGetValueSpy.mockReturnValue("test");
|
||||
expect(controller.incompatibleSetting).toBe(false);
|
||||
expect(incompatibleValueFn).toHaveBeenCalledWith("test");
|
||||
|
@ -64,7 +77,7 @@ describe("IncompatibleController", () => {
|
|||
describe("getValueOverride()", () => {
|
||||
it("returns forced value when setting is incompatible", () => {
|
||||
settingsGetValueSpy.mockReturnValue(true);
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null });
|
||||
const controller = new IncompatibleController("feature_spotlight" as FeatureSettingKey, { key: null });
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", true, SettingLevel.ACCOUNT),
|
||||
).toEqual({ key: null });
|
||||
|
@ -72,7 +85,7 @@ describe("IncompatibleController", () => {
|
|||
|
||||
it("returns null when setting is not incompatible", () => {
|
||||
settingsGetValueSpy.mockReturnValue(false);
|
||||
const controller = new IncompatibleController("feature_spotlight", { key: null });
|
||||
const controller = new IncompatibleController("feature_spotlight" as FeatureSettingKey, { key: null });
|
||||
expect(
|
||||
controller.getValueOverride(SettingLevel.ACCOUNT, "$room:server", true, SettingLevel.ACCOUNT),
|
||||
).toEqual(null);
|
||||
|
|
|
@ -11,7 +11,7 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||
|
||||
import ServerSupportUnstableFeatureController from "../../../../src/settings/controllers/ServerSupportUnstableFeatureController";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { LabGroup, SETTINGS } from "../../../../src/settings/Settings";
|
||||
import { FeatureSettingKey, LabGroup, SETTINGS } from "../../../../src/settings/Settings";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { WatchManager } from "../../../../src/settings/WatchManager";
|
||||
import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController";
|
||||
|
@ -19,7 +19,7 @@ import { TranslationKey } from "../../../../src/languageHandler";
|
|||
|
||||
describe("ServerSupportUnstableFeatureController", () => {
|
||||
const watchers = new WatchManager();
|
||||
const setting = "setting_name";
|
||||
const setting = "setting_name" as FeatureSettingKey;
|
||||
|
||||
async function prepareSetting(
|
||||
cli: MatrixClient,
|
||||
|
|
|
@ -17,7 +17,7 @@ describe("SystemFontController", () => {
|
|||
it("dispatches a system font update action on change", () => {
|
||||
const controller = new SystemFontController();
|
||||
|
||||
const getValueSpy = jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
|
||||
const getValueSpy = jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName): any => {
|
||||
if (settingName === "useBundledEmojiFont") return false;
|
||||
if (settingName === "useSystemFont") return true;
|
||||
if (settingName === "systemFont") return "Comic Sans MS";
|
||||
|
|
|
@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import ThemeWatcher from "../../../../src/settings/watchers/ThemeWatcher";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { SettingKey, Settings } from "../../../../src/settings/Settings.tsx";
|
||||
|
||||
function makeMatchMedia(values: any) {
|
||||
class FakeMediaQueryList {
|
||||
|
@ -33,8 +34,12 @@ function makeMatchMedia(values: any) {
|
|||
};
|
||||
}
|
||||
|
||||
function makeGetValue(values: any) {
|
||||
return function getValue<T = any>(settingName: string, _roomId: string | null = null, _excludeDefault = false): T {
|
||||
function makeGetValue(values: any): any {
|
||||
return function getValue<S extends SettingKey>(
|
||||
settingName: S,
|
||||
_roomId: string | null = null,
|
||||
_excludeDefault = false,
|
||||
): Settings[S] {
|
||||
return values[settingName];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { getMockClientWithEventEmitter, mockClientMethodsCrypto, mockPlatformPeg
|
|||
import { collectBugReport } from "../../src/rageshake/submit-rageshake";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { ConsoleLogger } from "../../src/rageshake/rageshake";
|
||||
import { FeatureSettingKey, SettingKey } from "../../src/settings/Settings.tsx";
|
||||
|
||||
describe("Rageshakes", () => {
|
||||
const RUST_CRYPTO_VERSION = "Rust SDK 0.7.0 (691ec63), Vodozemac 0.5.0";
|
||||
|
@ -376,8 +377,11 @@ describe("Rageshakes", () => {
|
|||
const mockSettingsStore = mocked(SettingsStore);
|
||||
|
||||
it("should collect labs from settings store", async () => {
|
||||
const someFeatures: string[] = ["feature_video_rooms", "feature_notification_settings2"];
|
||||
const enabledFeatures: string[] = ["feature_video_rooms"];
|
||||
const someFeatures = [
|
||||
"feature_video_rooms",
|
||||
"feature_notification_settings2",
|
||||
] as unknown[] as FeatureSettingKey[];
|
||||
const enabledFeatures: SettingKey[] = ["feature_video_rooms"];
|
||||
jest.spyOn(mockSettingsStore, "getFeatureSettingNames").mockReturnValue(someFeatures);
|
||||
jest.spyOn(mockSettingsStore, "getValue").mockImplementation((settingName): any => {
|
||||
return enabledFeatures.includes(settingName);
|
||||
|
|
|
@ -149,7 +149,7 @@ describe("theme", () => {
|
|||
});
|
||||
|
||||
it("should be robust to malformed custom_themes values", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue([23]);
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue([23] as any);
|
||||
expect(enumerateThemes()).toEqual({
|
||||
"light": "Light",
|
||||
"light-high-contrast": "Light high contrast",
|
||||
|
|
|
@ -44,7 +44,7 @@ describe("PlainTextExport", () => {
|
|||
[24, false, "Fri, Apr 16, 2021, 17:20:00 - @alice:example.com: Hello, world!\n"],
|
||||
[12, true, "Fri, Apr 16, 2021, 5:20:00 PM - @alice:example.com: Hello, world!\n"],
|
||||
])("should return text with %i hr time format", async (hour: number, setting: boolean, expectedMessage: string) => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) =>
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string): any =>
|
||||
settingName === "showTwelveHourTimestamps" ? setting : undefined,
|
||||
);
|
||||
const events: MatrixEvent[] = [
|
||||
|
|
Loading…
Reference in New Issue