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
This commit is contained in:
Michael Telatynski 2022-06-10 22:38:50 +01:00 committed by GitHub
parent 9b8b1d193e
commit ba2ce5ecba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 220 deletions

View file

@ -46,6 +46,17 @@ export enum UpdateCheckStatus {
Ready = "READY", 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"; const UPDATE_DEFER_KEY = "mx_defer_update";
/** /**
@ -225,79 +236,21 @@ export default abstract class BasePlatform {
*/ */
public abstract getAppVersion(): Promise<string>; public abstract getAppVersion(): Promise<string>;
/*
* 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 * Restarts the application, without necessarily reloading
* any application code * any application code
*/ */
public abstract reload(): void; public abstract reload(): void;
public supportsAutoLaunch(): boolean { public supportsSetting(settingName?: string): boolean {
return false; return false;
} }
// XXX: Surely this should be a setting like any other? public getSettingValue(settingName: string): Promise<any> {
public async getAutoLaunchEnabled(): Promise<boolean> { return undefined;
return false;
} }
public async setAutoLaunchEnabled(enabled: boolean): Promise<void> { public setSettingValue(settingName: string, value: any): Promise<void> {
throw new Error("Unimplemented");
}
public supportsWarnBeforeExit(): boolean {
return false;
}
public async shouldWarnBeforeExit(): Promise<boolean> {
return false;
}
public async setWarnBeforeExit(enabled: boolean): Promise<void> {
throw new Error("Unimplemented");
}
public supportsAutoHideMenuBar(): boolean {
return false;
}
public async getAutoHideMenuBarEnabled(): Promise<boolean> {
return false;
}
public async setAutoHideMenuBarEnabled(enabled: boolean): Promise<void> {
throw new Error("Unimplemented");
}
public supportsMinimizeToTray(): boolean {
return false;
}
public async getMinimizeToTrayEnabled(): Promise<boolean> {
return false;
}
public async setMinimizeToTrayEnabled(enabled: boolean): Promise<void> {
throw new Error("Unimplemented");
}
public supportsTogglingHardwareAcceleration(): boolean {
return false;
}
public async getHardwareAccelerationEnabled(): Promise<boolean> {
return true;
}
public async setHardwareAccelerationEnabled(enabled: boolean): Promise<void> {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }

View file

@ -33,6 +33,7 @@ interface IProps {
// XXX: once design replaces all toggles make this the default // XXX: once design replaces all toggles make this the default
useCheckbox?: boolean; useCheckbox?: boolean;
disabled?: boolean; disabled?: boolean;
hideIfCannotSet?: boolean;
onChange?(checked: boolean): void; onChange?(checked: boolean): void;
} }
@ -76,6 +77,8 @@ export default class SettingsFlag extends React.Component<IProps, IState> {
public render() { public render() {
const canChange = SettingsStore.canSetValue(this.props.name, this.props.roomId, this.props.level); 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 const label = this.props.label
? _t(this.props.label) ? _t(this.props.label)
: SettingsStore.getDisplayName(this.props.name, this.props.level); : SettingsStore.getDisplayName(this.props.name, this.props.level);

View file

@ -18,10 +18,8 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from "../../../../../languageHandler"; import { _t } from "../../../../../languageHandler";
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
import SettingsStore from "../../../../../settings/SettingsStore"; import SettingsStore from "../../../../../settings/SettingsStore";
import Field from "../../../elements/Field"; import Field from "../../../elements/Field";
import PlatformPeg from "../../../../../PlatformPeg";
import { SettingLevel } from "../../../../../settings/SettingLevel"; import { SettingLevel } from "../../../../../settings/SettingLevel";
import SettingsFlag from '../../../elements/SettingsFlag'; import SettingsFlag from '../../../elements/SettingsFlag';
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
@ -36,16 +34,6 @@ interface IProps {
} }
interface IState { interface IState {
autoLaunch: boolean;
autoLaunchSupported: boolean;
warnBeforeExit: boolean;
warnBeforeExitSupported: boolean;
alwaysShowMenuBarSupported: boolean;
alwaysShowMenuBar: boolean;
minimizeToTraySupported: boolean;
minimizeToTray: boolean;
togglingHardwareAccelerationSupported: boolean;
enableHardwareAcceleration: boolean;
autocompleteDelay: string; autocompleteDelay: string;
readMarkerInViewThresholdMs: string; readMarkerInViewThresholdMs: string;
readMarkerOutOfViewThresholdMs: string; readMarkerOutOfViewThresholdMs: string;
@ -112,16 +100,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
super(props); super(props);
this.state = { this.state = {
autoLaunch: false,
autoLaunchSupported: false,
warnBeforeExit: true,
warnBeforeExitSupported: false,
alwaysShowMenuBar: true,
alwaysShowMenuBarSupported: false,
minimizeToTray: true,
minimizeToTraySupported: false,
enableHardwareAcceleration: true,
togglingHardwareAccelerationSupported: false,
autocompleteDelay: autocompleteDelay:
SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay').toString(10), SettingsStore.getValueAt(SettingLevel.DEVICE, 'autocompleteDelay').toString(10),
readMarkerInViewThresholdMs: readMarkerInViewThresholdMs:
@ -131,74 +109,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
}; };
} }
async componentDidMount() {
const platform = PlatformPeg.get();
const autoLaunchSupported = await platform.supportsAutoLaunch();
let autoLaunch = false;
if (autoLaunchSupported) {
autoLaunch = await platform.getAutoLaunchEnabled();
}
const warnBeforeExitSupported = await platform.supportsWarnBeforeExit();
let warnBeforeExit = false;
if (warnBeforeExitSupported) {
warnBeforeExit = await platform.shouldWarnBeforeExit();
}
const alwaysShowMenuBarSupported = await platform.supportsAutoHideMenuBar();
let alwaysShowMenuBar = true;
if (alwaysShowMenuBarSupported) {
alwaysShowMenuBar = !(await platform.getAutoHideMenuBarEnabled());
}
const minimizeToTraySupported = await platform.supportsMinimizeToTray();
let minimizeToTray = true;
if (minimizeToTraySupported) {
minimizeToTray = await platform.getMinimizeToTrayEnabled();
}
const togglingHardwareAccelerationSupported = platform.supportsTogglingHardwareAcceleration();
let enableHardwareAcceleration = true;
if (togglingHardwareAccelerationSupported) {
enableHardwareAcceleration = await platform.getHardwareAccelerationEnabled();
}
this.setState({
autoLaunch,
autoLaunchSupported,
warnBeforeExit,
warnBeforeExitSupported,
alwaysShowMenuBarSupported,
alwaysShowMenuBar,
minimizeToTraySupported,
minimizeToTray,
togglingHardwareAccelerationSupported,
enableHardwareAcceleration,
});
}
private onAutoLaunchChange = (checked: boolean) => {
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<HTMLInputElement>) => { private onAutocompleteDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ autocompleteDelay: e.target.value }); this.setState({ autocompleteDelay: e.target.value });
SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value);
@ -232,49 +142,6 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
}; };
render() { render() {
let autoLaunchOption = null;
if (this.state.autoLaunchSupported) {
autoLaunchOption = <LabelledToggleSwitch
value={this.state.autoLaunch}
onChange={this.onAutoLaunchChange}
label={_t('Start automatically after system login')} />;
}
let warnBeforeExitOption = null;
if (this.state.warnBeforeExitSupported) {
warnBeforeExitOption = <LabelledToggleSwitch
value={this.state.warnBeforeExit}
onChange={this.onWarnBeforeExitChange}
label={_t('Warn before quitting')} />;
}
let autoHideMenuOption = null;
if (this.state.alwaysShowMenuBarSupported) {
autoHideMenuOption = <LabelledToggleSwitch
value={this.state.alwaysShowMenuBar}
onChange={this.onAlwaysShowMenuBarChange}
label={_t('Always show the window menu bar')} />;
}
let minimizeToTrayOption = null;
if (this.state.minimizeToTraySupported) {
minimizeToTrayOption = <LabelledToggleSwitch
value={this.state.minimizeToTray}
onChange={this.onMinimizeToTrayChange}
label={_t('Show tray icon and minimise window to it on close')} />;
}
let hardwareAccelerationOption = null;
if (this.state.togglingHardwareAccelerationSupported) {
const appName = SdkConfig.get().brand;
hardwareAccelerationOption = <LabelledToggleSwitch
value={this.state.enableHardwareAcceleration}
onChange={this.onHardwareAccelerationChange}
label={_t('Enable hardware acceleration (restart %(appName)s to take effect)', {
appName,
})} />;
}
return ( return (
<div className="mx_SettingsTab mx_PreferencesUserSettingsTab"> <div className="mx_SettingsTab mx_PreferencesUserSettingsTab">
<div className="mx_SettingsTab_heading">{ _t("Preferences") }</div> <div className="mx_SettingsTab_heading">{ _t("Preferences") }</div>
@ -331,11 +198,20 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
<div className="mx_SettingsTab_section"> <div className="mx_SettingsTab_section">
<span className="mx_SettingsTab_subheading">{ _t("General") }</span> <span className="mx_SettingsTab_subheading">{ _t("General") }</span>
{ this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS) } { this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS) }
{ minimizeToTrayOption }
{ hardwareAccelerationOption } <SettingsFlag name="Electron.showTrayIcon" level={SettingLevel.PLATFORM} hideIfCannotSet />
{ autoHideMenuOption } <SettingsFlag
{ autoLaunchOption } name="Electron.enableHardwareAcceleration"
{ warnBeforeExitOption } level={SettingLevel.PLATFORM}
hideIfCannotSet
label={_t('Enable hardware acceleration (restart %(appName)s to take effect)', {
appName: SdkConfig.get().brand,
})}
/>
<SettingsFlag name="Electron.alwaysShowMenuBar" level={SettingLevel.PLATFORM} hideIfCannotSet />
<SettingsFlag name="Electron.autoLaunch" level={SettingLevel.PLATFORM} hideIfCannotSet />
<SettingsFlag name="Electron.warnBeforeExit" level={SettingLevel.PLATFORM} hideIfCannotSet />
<Field <Field
label={_t('Autocomplete delay (ms)')} label={_t('Autocomplete delay (ms)')}
type='number' type='number'

View file

@ -16,18 +16,8 @@ limitations under the License.
import { ActionPayload } from "../payloads"; import { ActionPayload } from "../payloads";
import { Action } from "../actions"; import { Action } from "../actions";
import { UpdateCheckStatus } from "../../BasePlatform"; import { UpdateStatus } from "../../BasePlatform";
export interface CheckUpdatesPayload extends ActionPayload { export interface CheckUpdatesPayload extends ActionPayload, UpdateStatus {
action: Action.CheckUpdates; action: Action.CheckUpdates;
/**
* The current phase of the manual update check.
*/
status: UpdateCheckStatus;
/**
* Detail string relating to the current status, typically for error details.
*/
detail?: string;
} }

View file

@ -977,6 +977,11 @@
"Automatically send debug logs on any error": "Automatically send debug logs on any error", "Automatically send debug logs on any error": "Automatically send debug logs on any error",
"Automatically send debug logs on decryption errors": "Automatically send debug logs on decryption errors", "Automatically send debug logs on decryption errors": "Automatically send debug logs on decryption errors",
"Automatically send debug logs when key backup is not functioning": "Automatically send debug logs when key backup is not functioning", "Automatically send debug logs when key backup is not functioning": "Automatically send debug logs when key backup is not functioning",
"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": "Enable hardware acceleration",
"Partial Support for Threads": "Partial Support for Threads", "Partial Support for Threads": "Partial Support for Threads",
"Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. <a>Learn more</a>.": "Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. <a>Learn more</a>.", "Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. <a>Learn more</a>.": "Your homeserver does not currently support threads, so this feature may be unreliable. Some threaded messages may not be reliably available. <a>Learn more</a>.",
"Do you want to enable threads anyway?": "Do you want to enable threads anyway?", "Do you want to enable threads anyway?": "Do you want to enable threads anyway?",
@ -1505,11 +1510,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.", "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", "Room ID or address of ban list": "Room ID or address of ban list",
"Subscribe": "Subscribe", "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", "Preferences": "Preferences",
"Room list": "Room list", "Room list": "Room list",
"Keyboard shortcuts": "Keyboard shortcuts", "Keyboard shortcuts": "Keyboard shortcuts",
@ -1519,6 +1519,7 @@
"Code blocks": "Code blocks", "Code blocks": "Code blocks",
"Images, GIFs and videos": "Images, GIFs and videos", "Images, GIFs and videos": "Images, GIFs and videos",
"Timeline": "Timeline", "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)", "Autocomplete delay (ms)": "Autocomplete delay (ms)",
"Read Marker lifetime (ms)": "Read Marker lifetime (ms)", "Read Marker lifetime (ms)": "Read Marker lifetime (ms)",
"Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)", "Read Marker off-screen lifetime (ms)": "Read Marker off-screen lifetime (ms)",

View file

@ -24,6 +24,7 @@ export enum SettingLevel {
ROOM_ACCOUNT = "room-account", ROOM_ACCOUNT = "room-account",
ACCOUNT = "account", ACCOUNT = "account",
ROOM = "room", ROOM = "room",
PLATFORM = "platform",
CONFIG = "config", CONFIG = "config",
DEFAULT = "default", DEFAULT = "default",
} }

View file

@ -1042,4 +1042,32 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_UI_FEATURE, supportedLevels: LEVELS_UI_FEATURE,
default: true, 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,
},
}; };

View file

@ -34,6 +34,7 @@ import { SettingLevel } from "./SettingLevel";
import SettingsHandler from "./handlers/SettingsHandler"; import SettingsHandler from "./handlers/SettingsHandler";
import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload"; import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayload";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import PlatformSettingsHandler from "./handlers/PlatformSettingsHandler";
const defaultWatchManager = new WatchManager(); const defaultWatchManager = new WatchManager();
@ -61,6 +62,7 @@ const LEVEL_HANDLERS = {
), ),
[SettingLevel.ACCOUNT]: new LocalEchoWrapper(new AccountSettingsHandler(defaultWatchManager), SettingLevel.ACCOUNT), [SettingLevel.ACCOUNT]: new LocalEchoWrapper(new AccountSettingsHandler(defaultWatchManager), SettingLevel.ACCOUNT),
[SettingLevel.ROOM]: new LocalEchoWrapper(new RoomSettingsHandler(defaultWatchManager), SettingLevel.ROOM), [SettingLevel.ROOM]: new LocalEchoWrapper(new RoomSettingsHandler(defaultWatchManager), SettingLevel.ROOM),
[SettingLevel.PLATFORM]: new LocalEchoWrapper(new PlatformSettingsHandler(), SettingLevel.PLATFORM),
[SettingLevel.CONFIG]: new ConfigSettingsHandler(featureNames), [SettingLevel.CONFIG]: new ConfigSettingsHandler(featureNames),
[SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings), [SettingLevel.DEFAULT]: new DefaultSettingsHandler(defaultSettings, invertedDefaultSettings),
}; };
@ -75,6 +77,14 @@ export const LEVEL_ORDER = [
SettingLevel.DEFAULT, 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 = ( export type CallbackFn = (
settingName: string, settingName: string,
roomId: string, roomId: string,
@ -316,7 +326,7 @@ export default class SettingsStore {
} }
const setting = SETTINGS[settingName]; 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); 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."); 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 if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default
const minIndex = levelOrder.indexOf(level); 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."); 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 if (!levelOrder.includes(SettingLevel.DEFAULT)) levelOrder.push(SettingLevel.DEFAULT); // always include default
const handlers = SettingsStore.getHandlers(settingName); const handlers = SettingsStore.getHandlers(settingName);

View file

@ -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<void> {
return PlatformPeg.get().setSettingValue(settingName, newValue);
}
public isSupported(): boolean {
return PlatformPeg.get().supportsSetting();
}
}