From 0841aca2a4720d9091284eeac74be182a3aa889c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 1 Jun 2022 17:14:43 +0200 Subject: [PATCH 1/6] Fix `CallView` crash (#8735) (cherry picked from commit 6574c5c3e2835c6cf25bed58e9e0b62a188dcf72) --- src/components/views/voip/CallView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index 296ebd79ae..b212a2d5ba 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -418,7 +418,8 @@ export default class CallView extends React.Component { const isScreensharing = call.isScreensharing(); const { primaryFeed, sidebarShown } = this.state; - const sharerName = primaryFeed.getMember().name; + const sharerName = primaryFeed?.getMember().name; + if (!sharerName) return; let text = isScreensharing ? _t("You are presenting") From ec9117dbd1b2df5939f5b188ac041266f9800560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 6 Jun 2022 08:31:20 +0200 Subject: [PATCH 2/6] Improve Typescript in `BasePlatform` (#8768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve Typescript in `BasePlatform` Signed-off-by: Šimon Brandner * `installUpdate()` should not be abstract Signed-off-by: Šimon Brandner (cherry picked from commit 125a265806aab82c5423003ee22d66132f23b20a) --- src/BasePlatform.ts | 95 ++++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 48 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 9de1122430..630d56f06b 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -63,11 +63,11 @@ export default abstract class BasePlatform { this.startUpdateCheck = this.startUpdateCheck.bind(this); } - abstract getConfig(): Promise; + public abstract getConfig(): Promise; - abstract getDefaultDeviceDisplayName(): string; + public abstract getDefaultDeviceDisplayName(): string; - protected onAction = (payload: ActionPayload) => { + protected onAction = (payload: ActionPayload): void => { switch (payload.action) { case 'on_client_not_viable': case Action.OnLoggedOut: @@ -77,24 +77,24 @@ export default abstract class BasePlatform { }; // Used primarily for Analytics - abstract getHumanReadableName(): string; + public abstract getHumanReadableName(): string; - setNotificationCount(count: number) { + public setNotificationCount(count: number): void { this.notificationCount = count; } - setErrorStatus(errorDidOccur: boolean) { + public setErrorStatus(errorDidOccur: boolean): void { this.errorDidOccur = errorDidOccur; } /** * Whether we can call checkForUpdate on this platform build */ - async canSelfUpdate(): Promise { + public async canSelfUpdate(): Promise { return false; } - startUpdateCheck() { + public startUpdateCheck(): void { hideUpdateToast(); localStorage.removeItem(UPDATE_DEFER_KEY); dis.dispatch({ @@ -107,8 +107,7 @@ export default abstract class BasePlatform { * Update the currently running app to the latest available version * and replace this instance of the app with the new version. */ - installUpdate() { - } + public installUpdate(): void {} /** * Check if the version update has been deferred and that deferment is still in effect @@ -130,7 +129,7 @@ export default abstract class BasePlatform { * Ignore the pending update and don't prompt about this version * until the next morning (8am). */ - deferUpdate(newVersion: string) { + public deferUpdate(newVersion: string): void { const date = new Date(Date.now() + 24 * 60 * 60 * 1000); date.setHours(8, 0, 0, 0); // set to next 8am localStorage.setItem(UPDATE_DEFER_KEY, JSON.stringify([newVersion, date.getTime()])); @@ -141,7 +140,7 @@ export default abstract class BasePlatform { * Return true if platform supports multi-language * spell-checking, otherwise false. */ - supportsMultiLanguageSpellCheck(): boolean { + public supportsMultiLanguageSpellCheck(): boolean { return false; } @@ -157,7 +156,7 @@ export default abstract class BasePlatform { * notifications, otherwise false. * @returns {boolean} whether the platform supports displaying notifications */ - supportsNotifications(): boolean { + public supportsNotifications(): boolean { return false; } @@ -166,7 +165,7 @@ export default abstract class BasePlatform { * to display notifications. Otherwise false. * @returns {boolean} whether the application has permission to display notifications */ - maySendNotifications(): boolean { + public maySendNotifications(): boolean { return false; } @@ -177,7 +176,7 @@ export default abstract class BasePlatform { * that is 'granted' if the user allowed the request or * 'denied' otherwise. */ - abstract requestNotificationPermission(): Promise; + public abstract requestNotificationPermission(): Promise; public displayNotification( title: string, @@ -211,10 +210,9 @@ export default abstract class BasePlatform { return notification; } - loudNotification(ev: MatrixEvent, room: Room) { - } + public loudNotification(ev: MatrixEvent, room: Room): void {} - clearNotification(notif: Notification) { + public clearNotification(notif: Notification): void { // Some browsers don't support this, e.g Safari on iOS // https://developer.mozilla.org/en-US/docs/Web/API/Notification/close if (notif.close) { @@ -225,14 +223,14 @@ export default abstract class BasePlatform { /** * Returns a promise that resolves to a string representing the current version of the application. */ - abstract getAppVersion(): Promise; + public abstract getAppVersion(): Promise; /* * If it's not expected that capturing the screen will work * with getUserMedia, return a string explaining why not. * Otherwise, return null. */ - screenCaptureErrorString(): string { + public screenCaptureErrorString(): string { return "Not implemented"; } @@ -240,54 +238,54 @@ export default abstract class BasePlatform { * Restarts the application, without necessarily reloading * any application code */ - abstract reload(); + public abstract reload(): void; - supportsAutoLaunch(): boolean { + public supportsAutoLaunch(): boolean { return false; } // XXX: Surely this should be a setting like any other? - async getAutoLaunchEnabled(): Promise { + public async getAutoLaunchEnabled(): Promise { return false; } - async setAutoLaunchEnabled(enabled: boolean): Promise { + public async setAutoLaunchEnabled(enabled: boolean): Promise { throw new Error("Unimplemented"); } - supportsWarnBeforeExit(): boolean { + public supportsWarnBeforeExit(): boolean { return false; } - async shouldWarnBeforeExit(): Promise { + public async shouldWarnBeforeExit(): Promise { return false; } - async setWarnBeforeExit(enabled: boolean): Promise { + public async setWarnBeforeExit(enabled: boolean): Promise { throw new Error("Unimplemented"); } - supportsAutoHideMenuBar(): boolean { + public supportsAutoHideMenuBar(): boolean { return false; } - async getAutoHideMenuBarEnabled(): Promise { + public async getAutoHideMenuBarEnabled(): Promise { return false; } - async setAutoHideMenuBarEnabled(enabled: boolean): Promise { + public async setAutoHideMenuBarEnabled(enabled: boolean): Promise { throw new Error("Unimplemented"); } - supportsMinimizeToTray(): boolean { + public supportsMinimizeToTray(): boolean { return false; } - async getMinimizeToTrayEnabled(): Promise { + public async getMinimizeToTrayEnabled(): Promise { return false; } - async setMinimizeToTrayEnabled(enabled: boolean): Promise { + public async setMinimizeToTrayEnabled(enabled: boolean): Promise { throw new Error("Unimplemented"); } @@ -309,23 +307,23 @@ export default abstract class BasePlatform { * @return {BaseEventIndexManager} The EventIndex manager for our platform, * can be null if the platform doesn't support event indexing. */ - getEventIndexingManager(): BaseEventIndexManager | null { + public getEventIndexingManager(): BaseEventIndexManager | null { return null; } - async setLanguage(preferredLangs: string[]) {} + public setLanguage(preferredLangs: string[]) {} - setSpellCheckLanguages(preferredLangs: string[]) {} + public setSpellCheckLanguages(preferredLangs: string[]) {} - getSpellCheckLanguages(): Promise | null { + public getSpellCheckLanguages(): Promise | null { return null; } - async getDesktopCapturerSources(options: GetSourcesOptions): Promise> { + public async getDesktopCapturerSources(options: GetSourcesOptions): Promise> { return []; } - supportsDesktopCapturer(): boolean { + public supportsDesktopCapturer(): boolean { return false; } @@ -335,7 +333,7 @@ export default abstract class BasePlatform { public navigateForwardBack(back: boolean): void {} - getAvailableSpellCheckLanguages(): Promise | null { + public getAvailableSpellCheckLanguages(): Promise | null { return null; } @@ -352,7 +350,12 @@ export default abstract class BasePlatform { * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. * @param {string} idpId The ID of the Identity Provider being targeted, optional. */ - startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string, idpId?: string) { + public startSingleSignOn( + mxClient: MatrixClient, + loginType: "sso" | "cas", + fragmentAfterLogin: string, + idpId?: string, + ): void { // persist hs url and is url for when the user is returned to the app with the login token localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); if (mxClient.getIdentityServerUrl()) { @@ -365,10 +368,6 @@ export default abstract class BasePlatform { window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType, idpId); // redirect to SSO } - onKeyDown(ev: KeyboardEvent): boolean { - return false; // no shortcuts implemented - } - /** * Get a previously stored pickle key. The pickle key is used for * encrypting libolm objects. @@ -377,7 +376,7 @@ export default abstract class BasePlatform { * @returns {string|null} the previously stored pickle key, or null if no * pickle key has been stored. */ - async getPickleKey(userId: string, deviceId: string): Promise { + public async getPickleKey(userId: string, deviceId: string): Promise { if (!window.crypto || !window.crypto.subtle) { return null; } @@ -423,7 +422,7 @@ export default abstract class BasePlatform { * @returns {string|null} the pickle key, or null if the platform does not * support storing pickle keys. */ - async createPickleKey(userId: string, deviceId: string): Promise { + public async createPickleKey(userId: string, deviceId: string): Promise { if (!window.crypto || !window.crypto.subtle) { return null; } @@ -462,7 +461,7 @@ export default abstract class BasePlatform { * @param {string} userId the user ID for the user that the pickle key is for. * @param {string} userId the device ID that the pickle key is for. */ - async destroyPickleKey(userId: string, deviceId: string): Promise { + public async destroyPickleKey(userId: string, deviceId: string): Promise { try { await idbDelete("pickleKey", [userId, deviceId]); } catch (e) { From d6c162a5cfb7a7c2544a9f1ee3ab58118a99965a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 10 Jun 2022 22:38:50 +0100 Subject: [PATCH 3/6] Abstract electron settings properly to avoid boilerplate-hell (#8798) * Remove unused method `BasePlatform::screenCaptureErrorString` * Improve platform typescripting * Remove redundant awaits * Abstract electron settings properly to avoid boilerplate-hell * i18n * Fix stray semi-colons * Fix setting level order for Platform settings (cherry picked from commit ba2ce5ecba2982ae6587e586e71384e60f3b224d) --- src/BasePlatform.ts | 77 ++------- .../views/elements/SettingsFlag.tsx | 3 + .../tabs/user/PreferencesUserSettingsTab.tsx | 152 ++---------------- .../payloads/CheckUpdatesPayload.ts | 14 +- src/i18n/strings/en_EN.json | 11 +- src/settings/SettingLevel.ts | 1 + src/settings/Settings.tsx | 28 ++++ src/settings/SettingsStore.ts | 16 +- .../handlers/PlatformSettingsHandler.ts | 40 +++++ 9 files changed, 122 insertions(+), 220 deletions(-) create mode 100644 src/settings/handlers/PlatformSettingsHandler.ts diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 630d56f06b..3c83229755 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -46,6 +46,17 @@ export enum UpdateCheckStatus { Ready = "READY", } +export interface UpdateStatus { + /** + * The current phase of the manual update check. + */ + status: UpdateCheckStatus; + /** + * Detail string relating to the current status, typically for error details. + */ + detail?: string; +} + const UPDATE_DEFER_KEY = "mx_defer_update"; /** @@ -225,79 +236,21 @@ export default abstract class BasePlatform { */ public abstract getAppVersion(): Promise; - /* - * If it's not expected that capturing the screen will work - * with getUserMedia, return a string explaining why not. - * Otherwise, return null. - */ - public screenCaptureErrorString(): string { - return "Not implemented"; - } - /** * Restarts the application, without necessarily reloading * any application code */ public abstract reload(): void; - public supportsAutoLaunch(): boolean { + public supportsSetting(settingName?: string): boolean { return false; } - // XXX: Surely this should be a setting like any other? - public async getAutoLaunchEnabled(): Promise { - return false; + public getSettingValue(settingName: string): Promise { + return undefined; } - public async setAutoLaunchEnabled(enabled: boolean): Promise { - throw new Error("Unimplemented"); - } - - public supportsWarnBeforeExit(): boolean { - return false; - } - - public async shouldWarnBeforeExit(): Promise { - return false; - } - - public async setWarnBeforeExit(enabled: boolean): Promise { - throw new Error("Unimplemented"); - } - - public supportsAutoHideMenuBar(): boolean { - return false; - } - - public async getAutoHideMenuBarEnabled(): Promise { - return false; - } - - public async setAutoHideMenuBarEnabled(enabled: boolean): Promise { - throw new Error("Unimplemented"); - } - - public supportsMinimizeToTray(): boolean { - return false; - } - - public async getMinimizeToTrayEnabled(): Promise { - return false; - } - - public async setMinimizeToTrayEnabled(enabled: boolean): Promise { - throw new Error("Unimplemented"); - } - - public supportsTogglingHardwareAcceleration(): boolean { - return false; - } - - public async getHardwareAccelerationEnabled(): Promise { - return true; - } - - public async setHardwareAccelerationEnabled(enabled: boolean): Promise { + public setSettingValue(settingName: string, value: any): Promise { throw new Error("Unimplemented"); } diff --git a/src/components/views/elements/SettingsFlag.tsx b/src/components/views/elements/SettingsFlag.tsx index 3437440f00..f433613a77 100644 --- a/src/components/views/elements/SettingsFlag.tsx +++ b/src/components/views/elements/SettingsFlag.tsx @@ -33,6 +33,7 @@ interface IProps { // XXX: once design replaces all toggles make this the default useCheckbox?: boolean; disabled?: boolean; + hideIfCannotSet?: boolean; onChange?(checked: boolean): void; } @@ -76,6 +77,8 @@ export default class SettingsFlag extends React.Component { public render() { const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level); + if (!canChange && this.props.hideIfCannotSet) return null; + const label = this.props.label ? _t(this.props.label) : SettingsStore.getDisplayName(this.props.name, this.props.level); diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 64f052b1ac..2b67d22c0d 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -18,10 +18,8 @@ limitations under the License. import React from 'react'; import { _t } from "../../../../../languageHandler"; -import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch"; import SettingsStore from "../../../../../settings/SettingsStore"; import Field from "../../../elements/Field"; -import PlatformPeg from "../../../../../PlatformPeg"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import SettingsFlag from '../../../elements/SettingsFlag'; import AccessibleButton from "../../../elements/AccessibleButton"; @@ -36,16 +34,6 @@ interface IProps { } interface IState { - autoLaunch: boolean; - autoLaunchSupported: boolean; - warnBeforeExit: boolean; - warnBeforeExitSupported: boolean; - alwaysShowMenuBarSupported: boolean; - alwaysShowMenuBar: boolean; - minimizeToTraySupported: boolean; - minimizeToTray: boolean; - togglingHardwareAccelerationSupported: boolean; - enableHardwareAcceleration: boolean; autocompleteDelay: string; readMarkerInViewThresholdMs: string; readMarkerOutOfViewThresholdMs: string; @@ -112,16 +100,6 @@ export default class PreferencesUserSettingsTab extends React.Component { - PlatformPeg.get().setAutoLaunchEnabled(checked).then(() => this.setState({ autoLaunch: checked })); - }; - - private onWarnBeforeExitChange = (checked: boolean) => { - PlatformPeg.get().setWarnBeforeExit(checked).then(() => this.setState({ warnBeforeExit: checked })); - }; - - private onAlwaysShowMenuBarChange = (checked: boolean) => { - PlatformPeg.get().setAutoHideMenuBarEnabled(!checked).then(() => this.setState({ alwaysShowMenuBar: checked })); - }; - - private onMinimizeToTrayChange = (checked: boolean) => { - PlatformPeg.get().setMinimizeToTrayEnabled(checked).then(() => this.setState({ minimizeToTray: checked })); - }; - - private onHardwareAccelerationChange = (checked: boolean) => { - PlatformPeg.get().setHardwareAccelerationEnabled(checked).then( - () => this.setState({ enableHardwareAcceleration: checked })); - }; - private onAutocompleteDelayChange = (e: React.ChangeEvent) => { this.setState({ autocompleteDelay: e.target.value }); SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); @@ -232,49 +142,6 @@ export default class PreferencesUserSettingsTab extends React.Component; - } - - let warnBeforeExitOption = null; - if (this.state.warnBeforeExitSupported) { - warnBeforeExitOption = ; - } - - let autoHideMenuOption = null; - if (this.state.alwaysShowMenuBarSupported) { - autoHideMenuOption = ; - } - - let minimizeToTrayOption = null; - if (this.state.minimizeToTraySupported) { - minimizeToTrayOption = ; - } - - let hardwareAccelerationOption = null; - if (this.state.togglingHardwareAccelerationSupported) { - const appName = SdkConfig.get().brand; - hardwareAccelerationOption = ; - } - return (
{ _t("Preferences") }
@@ -331,11 +198,20 @@ export default class PreferencesUserSettingsTab extends React.Component { _t("General") } { this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS) } - { minimizeToTrayOption } - { hardwareAccelerationOption } - { autoHideMenuOption } - { autoLaunchOption } - { warnBeforeExitOption } + + + + + + + Learn more.": "Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. Learn more.", "Do you want to enable threads anyway?": "Do you want to enable threads anyway?", @@ -1498,11 +1503,6 @@ "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.", "Room ID or address of ban list": "Room ID or address of ban list", "Subscribe": "Subscribe", - "Start automatically after system login": "Start automatically after system login", - "Warn before quitting": "Warn before quitting", - "Always show the window menu bar": "Always show the window menu bar", - "Show tray icon and minimise window to it on close": "Show tray icon and minimise window to it on close", - "Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)", "Preferences": "Preferences", "Room list": "Room list", "Keyboard shortcuts": "Keyboard shortcuts", @@ -1512,6 +1512,7 @@ "Code blocks": "Code blocks", "Images, GIFs and videos": "Images, GIFs and videos", "Timeline": "Timeline", + "Enable hardware acceleration (restart %(appName)s to take effect)": "Enable hardware acceleration (restart %(appName)s to take effect)", "Autocomplete delay (ms)": "Autocomplete delay (ms)", "Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", diff --git a/src/settings/SettingLevel.ts b/src/settings/SettingLevel.ts index e4703be1a9..66d3834ac6 100644 --- a/src/settings/SettingLevel.ts +++ b/src/settings/SettingLevel.ts @@ -24,6 +24,7 @@ export enum SettingLevel { ROOM_ACCOUNT = "room-account", ACCOUNT = "account", ROOM = "room", + PLATFORM = "platform", CONFIG = "config", DEFAULT = "default", } diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 09987c4386..f9c9bad314 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -1008,4 +1008,32 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_UI_FEATURE, default: true, }, + + // Electron-specific settings, they are stored by Electron and set/read over an IPC. + // We store them over there are they are necessary to know before the renderer process launches. + "Electron.autoLaunch": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("Start automatically after system login"), + default: false, + }, + "Electron.warnBeforeExit": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("Warn before quitting"), + default: true, + }, + "Electron.alwaysShowMenuBar": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("Always show the window menu bar"), + default: false, + }, + "Electron.showTrayIcon": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("Show tray icon and minimise window to it on close"), + default: true, + }, + "Electron.enableHardwareAcceleration": { + supportedLevels: [SettingLevel.PLATFORM], + displayName: _td("Enable hardware acceleration"), + default: true, + }, }; diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 19a419afb7..4f81ad3be6 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -34,6 +34,7 @@ import { SettingLevel } from "./SettingLevel"; import SettingsHandler from "./handlers/SettingsHandler"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; import { Action } from "../dispatcher/actions"; +import PlatformSettingsHandler from "./handlers/PlatformSettingsHandler"; const defaultWatchManager = new WatchManager(); @@ -61,6 +62,7 @@ const LEVEL_HANDLERS = { ), [SettingLevel.ACCOUNT]: new LocalEchoWrapper(new AccountSettingsHandler(defaultWatchManager), SettingLevel.ACCOUNT), [SettingLevel.ROOM]: new LocalEchoWrapper(new RoomSettingsHandler(defaultWatchManager), SettingLevel.ROOM), + [SettingLevel.PLATFORM]: new LocalEchoWrapper(new PlatformSettingsHandler(), SettingLevel.PLATFORM), [SettingLevel.CONFIG]: new ConfigSettingsHandler(featureNames), [SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings), }; @@ -75,6 +77,14 @@ export const LEVEL_ORDER = [ SettingLevel.DEFAULT, ]; +function getLevelOrder(setting: ISetting): SettingLevel[] { + // Settings which support only a single setting level are inherently ordered + if (setting.supportedLevelsAreOrdered || setting.supportedLevels.length === 1) { + return setting.supportedLevels; + } + return LEVEL_ORDER; +} + export type CallbackFn = ( settingName: string, roomId: string, @@ -316,7 +326,7 @@ export default class SettingsStore { } const setting = SETTINGS[settingName]; - const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER); + const levelOrder = getLevelOrder(setting); return SettingsStore.getValueAt(levelOrder[0], settingName, roomId, false, excludeDefault); } @@ -345,7 +355,7 @@ export default class SettingsStore { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); } - const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER); + const levelOrder = getLevelOrder(setting); if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default const minIndex = levelOrder.indexOf(level); @@ -518,7 +528,7 @@ export default class SettingsStore { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); } - const levelOrder = (setting.supportedLevelsAreOrdered ? setting.supportedLevels : LEVEL_ORDER); + const levelOrder = getLevelOrder(setting); if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default const handlers = SettingsStore.getHandlers(settingName); diff --git a/src/settings/handlers/PlatformSettingsHandler.ts b/src/settings/handlers/PlatformSettingsHandler.ts new file mode 100644 index 0000000000..ffdc731c2f --- /dev/null +++ b/src/settings/handlers/PlatformSettingsHandler.ts @@ -0,0 +1,40 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import SettingsHandler from "./SettingsHandler"; +import PlatformPeg from "../../PlatformPeg"; + +/** + * Gets and sets settings at the "platform" level for the current device. + * This handler does not make use of the roomId parameter. + */ +export default class PlatformSettingsHandler extends SettingsHandler { + public canSetValue(settingName: string, roomId: string): boolean { + return PlatformPeg.get().supportsSetting(settingName); + } + + public getValue(settingName: string, roomId: string): any { + return PlatformPeg.get().getSettingValue(settingName); + } + + public setValue(settingName: string, roomId: string, newValue: any): Promise { + return PlatformPeg.get().setSettingValue(settingName, newValue); + } + + public isSupported(): boolean { + return PlatformPeg.get().supportsSetting(); + } +} From 6e786d6cd54f3654ad5460d8a5d5f743e7652795 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Mon, 13 Jun 2022 08:03:16 +0200 Subject: [PATCH 4/6] Prevent level order to be modified (#8821) (cherry picked from commit b87c53788501eb84b913caf61fa04ce1645c9e70) --- src/settings/SettingsStore.ts | 5 ++- test/settings/SettingsStore-test.ts | 61 +++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 test/settings/SettingsStore-test.ts diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 4f81ad3be6..b0d2bb75f3 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -80,7 +80,8 @@ export const LEVEL_ORDER = [ function getLevelOrder(setting: ISetting): SettingLevel[] { // Settings which support only a single setting level are inherently ordered if (setting.supportedLevelsAreOrdered || setting.supportedLevels.length === 1) { - return setting.supportedLevels; + // return a copy to prevent callers from modifying the array + return [...setting.supportedLevels]; } return LEVEL_ORDER; } @@ -359,7 +360,7 @@ export default class SettingsStore { if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default const minIndex = levelOrder.indexOf(level); - if (minIndex === -1) throw new Error("Level " + level + " is not prioritized"); + if (minIndex === -1) throw new Error(`Level "${level}" for setting "${settingName}" is not prioritized`); const handlers = SettingsStore.getHandlers(settingName); diff --git a/test/settings/SettingsStore-test.ts b/test/settings/SettingsStore-test.ts new file mode 100644 index 0000000000..e35f2a25b1 --- /dev/null +++ b/test/settings/SettingsStore-test.ts @@ -0,0 +1,61 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import BasePlatform from "../../src/BasePlatform"; +import { SettingLevel } from "../../src/settings/SettingLevel"; +import SettingsStore from "../../src/settings/SettingsStore"; +import { mockPlatformPeg } from "../test-utils"; + +const TEST_DATA = [ + { + name: "Electron.showTrayIcon", + level: SettingLevel.PLATFORM, + value: true, + }, +]; + +describe("SettingsStore", () => { + let platformSettings: object; + + beforeAll(() => { + jest.clearAllMocks(); + platformSettings = {}; + mockPlatformPeg({ + isLevelSupported: jest.fn().mockReturnValue(true), + supportsSetting: jest.fn().mockReturnValue(true), + setSettingValue: jest.fn().mockImplementation((settingName: string, value: any) => { + platformSettings[settingName] = value; + }), + getSettingValue: jest.fn().mockImplementation((settingName: string) => { + return platformSettings[settingName]; + }), + } as unknown as BasePlatform); + + TEST_DATA.forEach(d => { + SettingsStore.setValue(d.name, null, d.level, d.value); + }); + }); + + describe("getValueAt", () => { + TEST_DATA.forEach(d => { + it(`should return the value "${d.level}"."${d.name}"`, () => { + expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value); + // regression test #22545 + expect(SettingsStore.getValueAt(d.level, d.name)).toBe(d.value); + }); + }); + }); +}); From 985f9e353c08cef538e0d20b94b436fcab14afa2 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 14 Jun 2022 14:05:10 +0100 Subject: [PATCH 5/6] Prepare changelog for v3.47.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3474847522..2b2b3ec297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [3.47.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.47.0) (2022-06-14) +===================================================================================================== + +## 🐛 Bug Fixes + * Fix CallView crash ([\#8735](https://github.com/matrix-org/matrix-react-sdk/pull/8735)). Contributed by @SimonBrandner. + * Fix missing element desktop preferences ([\#8798](https://github.com/matrix-org/matrix-react-sdk/pull/8798)). Contributed by @t3chguy. + + Changes in [3.46.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.46.0) (2022-06-07) ===================================================================================================== From 7ed87092c9ab0e793d01b3871f5e64fb43770869 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Tue, 14 Jun 2022 14:05:11 +0100 Subject: [PATCH 6/6] v3.47.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba40a3a618..6c4a2cde0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.46.0", + "version": "3.47.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": {