From acd9d6d86778eec805a38ce9ca6352dcb05af746 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 6 Nov 2025 17:23:47 +0000 Subject: [PATCH 1/3] Add new incompatible controllers. --- .../CombinationIncompatbleController.ts | 54 ++++++++++++++++++ .../IncompatibleConfigController.ts | 57 +++++++++++++++++++ .../controllers/IncompatibleController.ts | 5 +- 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/settings/controllers/CombinationIncompatbleController.ts create mode 100644 src/settings/controllers/IncompatibleConfigController.ts diff --git a/src/settings/controllers/CombinationIncompatbleController.ts b/src/settings/controllers/CombinationIncompatbleController.ts new file mode 100644 index 00000000000..c2432eb01d2 --- /dev/null +++ b/src/settings/controllers/CombinationIncompatbleController.ts @@ -0,0 +1,54 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2021 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import SettingController from "./SettingController.ts"; +import { type SettingLevel } from "../SettingLevel.ts"; +import IncompatibleController from "./IncompatibleController.ts"; +import IncompatibleConfigController from "./IncompatibleConfigController.ts"; + +/** + * Enforces that a boolean setting cannot be enabled if the incompatible setting + * is also enabled, to prevent cascading undefined behaviour between conflicting + * labs flags. + */ +export default class CombinationIncompatbleController extends SettingController { + public constructor( + private readonly controllers: (IncompatibleConfigController|IncompatibleController)[] + ) { + super(); + } + + public getValueOverride( + level: SettingLevel, + roomId: string, + calculatedValue: any, + calculatedAtLevel: SettingLevel | null, + ): any { + for (const controller of this.controllers) { + const res = controller.getValueOverride(level, roomId, calculatedValue, calculatedAtLevel); + if (res !== null) { + return res; + } + } + return null; + } + + public get settingDisabled(): boolean|string { + for (const controller of this.controllers) { + const res = controller.settingDisabled; + if (res) { + return res; + } + } + return false; + } + + public get incompatibleSetting(): boolean { + return this.controllers.some(s => s.incompatibleSetting); + } +} diff --git a/src/settings/controllers/IncompatibleConfigController.ts b/src/settings/controllers/IncompatibleConfigController.ts new file mode 100644 index 00000000000..fb05595997f --- /dev/null +++ b/src/settings/controllers/IncompatibleConfigController.ts @@ -0,0 +1,57 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2021 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import SettingController from "./SettingController.ts"; +import { type SettingLevel } from "../SettingLevel.ts"; +import { IConfigOptions } from "../../IConfigOptions.ts"; +import SdkConfig from "../../SdkConfig.ts"; + +/** + * Enforces that a boolean setting cannot be enabled if the incompatible setting + * is also enabled, to prevent cascading undefined behaviour between conflicting + * labs flags. + */ +export default class IncompatibleConfigController extends SettingController { + public constructor( + private readonly getSetting: (c: IConfigOptions) => boolean, + private forcedValue: any = false, + private incompatibleValue: any | ((v: any) => boolean) = true, + private readonly disabledString?: string, + ) { + super(); + console.log(SdkConfig.get()); + } + + public getValueOverride( + level: SettingLevel, + roomId: string, + calculatedValue: any, + calculatedAtLevel: SettingLevel | null, + ): any { + if (this.incompatibleSetting) { + return this.forcedValue; + } + return null; // no override + } + + public get configSettingValue(): boolean { + return this.getSetting(SdkConfig.get()); + } + + public get settingDisabled(): boolean|string { + console.log("IncompatibleConfigController", this.configSettingValue, this.incompatibleSetting ? (this.disabledString ?? true) : false); + return this.incompatibleSetting ? (this.disabledString ?? true) : false; + } + + public get incompatibleSetting(): boolean { + if (typeof this.incompatibleValue === "function") { + return this.incompatibleValue(this.configSettingValue); + } + return this.configSettingValue === this.incompatibleValue; + } +} diff --git a/src/settings/controllers/IncompatibleController.ts b/src/settings/controllers/IncompatibleController.ts index 81c8ca74ac0..503a2968c17 100644 --- a/src/settings/controllers/IncompatibleController.ts +++ b/src/settings/controllers/IncompatibleController.ts @@ -21,6 +21,7 @@ export default class IncompatibleController extends SettingController { private settingName: BooleanSettingKey, private forcedValue: any = false, private incompatibleValue: any | ((v: any) => boolean) = true, + private readonly disabledString?: string, ) { super(); } @@ -37,8 +38,8 @@ export default class IncompatibleController extends SettingController { return null; // no override } - public get settingDisabled(): boolean { - return this.incompatibleSetting; + public get settingDisabled(): boolean|string { + return this.incompatibleSetting ? (this.disabledString ?? true) : false; } public get incompatibleSetting(): boolean { From b9c3ccba889e90d73f15a85e3d9dee92a13e8455 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 6 Nov 2025 17:23:57 +0000 Subject: [PATCH 2/3] Fixup Element Call labs setting --- src/settings/Settings.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 2b2a57c2c58..5dabc099f4b 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -50,6 +50,8 @@ import { SortingAlgorithm } from "../stores/room-list-v3/skip-list/sorters/index import MediaPreviewConfigController from "./controllers/MediaPreviewConfigController.ts"; import InviteRulesConfigController from "./controllers/InviteRulesConfigController.ts"; import { type ComputedInviteConfig } from "../@types/invite-rules.ts"; +import IncompatibleConfigController from "./controllers/IncompatibleConfigController.ts"; +import CombinationIncompatbleController from "./controllers/CombinationIncompatbleController.ts"; export const defaultWatchManager = new WatchManager(); @@ -623,8 +625,13 @@ export const SETTINGS: Settings = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED, supportedLevelsAreOrdered: true, displayName: _td("labs|element_call_video_rooms"), - controller: new ReloadOnChangeController(), - default: false, + controller: new CombinationIncompatbleController([ + // Video rooms need to be enabled to enable this. + new IncompatibleController("feature_video_rooms", false, false, _t("labs|element_call_video_rooms_missing_video_rooms")), + // If the config only allows Element Call, force enable + new IncompatibleConfigController((c) => !!c.element_call?.use_exclusively, true, true, _t("labs|element_call_video_rooms_element_call_exclusive")), + ]), + default: true, }, "feature_group_calls": { isFeature: true, From 809a69666c26e4455c53aa10f0813642a925779d Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Thu, 6 Nov 2025 17:24:00 +0000 Subject: [PATCH 3/3] Add new error strings --- src/i18n/strings/en_EN.json | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 86f9197e927..70c763691ad 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1520,6 +1520,8 @@ "dynamic_room_predecessors": "Dynamic room predecessors", "dynamic_room_predecessors_description": "Enable MSC3946 (to support late-arriving room archives)", "element_call_video_rooms": "Element Call video rooms", + "element_call_video_rooms_element_call_exclusive": "Element is configured to use Element Call exlusively, so this cannot be disabled.", + "element_call_video_rooms_missing_video_rooms": "You must enable video rooms to enable this feature.", "exclude_insecure_devices": "Exclude insecure devices when sending/receiving messages", "exclude_insecure_devices_description": "When this mode is enabled, encrypted messages will not be shared with unverified devices, and messages from unverified devices will be shown as an error. Note that if you enable this mode, you may be unable to communicate with users who have not verified their devices.", "experimental_description": "Feeling experimental? Try out our latest ideas in development. These features are not finalised; they may be unstable, may change, or may be dropped altogether. Learn more.", @@ -3390,24 +3392,10 @@ "no_rooms_with_unread_threads": "You don't have rooms with unread threads yet." }, "time": { - "about_day_ago": "about a day ago", - "about_hour_ago": "about an hour ago", - "about_minute_ago": "about a minute ago", "date_at_time": "%(date)s at %(time)s", - "few_seconds_ago": "a few seconds ago", "hours_minutes_seconds_left": "%(hours)sh %(minutes)sm %(seconds)ss left", - "in_about_day": "about a day from now", - "in_about_hour": "about an hour from now", - "in_about_minute": "about a minute from now", - "in_few_seconds": "a few seconds from now", - "in_n_days": "%(num)s days from now", - "in_n_hours": "%(num)s hours from now", - "in_n_minutes": "%(num)s minutes from now", "left": "%(timeRemaining)s left", "minutes_seconds_left": "%(minutes)sm %(seconds)ss left", - "n_days_ago": "%(num)s days ago", - "n_hours_ago": "%(num)s hours ago", - "n_minutes_ago": "%(num)s minutes ago", "seconds_left": "%(seconds)ss left", "short_days": "%(value)sd", "short_days_hours_minutes_seconds": "%(days)sd %(hours)sh %(minutes)sm %(seconds)ss", @@ -3462,11 +3450,9 @@ "unable_to_find": "Tried to load a specific point in this room's timeline, but was unable to find it." }, "m.audio": { - "audio_player": "Audio player", "error_downloading_audio": "Error downloading audio", "error_processing_audio": "Error processing audio message", - "error_processing_voice_message": "Error processing voice message", - "unnamed_audio": "Unnamed audio" + "error_processing_voice_message": "Error processing voice message" }, "m.beacon_info": { "view_live_location": "View live location"