From 4ad32b16ea09633cae5699a728e6adb64138f37b Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 19 Oct 2021 14:50:09 +0100 Subject: [PATCH] Break out font size settings to a separate component --- res/css/_components.scss | 1 + res/css/views/settings/_FontScalingPanel.scss | 81 +++++ .../tabs/user/_AppearanceUserSettingsTab.scss | 59 ---- .../views/settings/FontScalingPanel.tsx | 172 ++++++++++ .../tabs/user/AppearanceUserSettingsTab.tsx | 91 +----- .../views/settings/FontScalingPanel-test.tsx | 38 +++ .../FontScalingPanel-test.tsx.snap | 307 ++++++++++++++++++ 7 files changed, 601 insertions(+), 148 deletions(-) create mode 100644 res/css/views/settings/_FontScalingPanel.scss create mode 100644 src/components/views/settings/FontScalingPanel.tsx create mode 100644 test/components/views/settings/FontScalingPanel-test.tsx create mode 100644 test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap diff --git a/res/css/_components.scss b/res/css/_components.scss index ebf95a1c4a..8ea46c4243 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -248,6 +248,7 @@ @import "./views/settings/_DevicesPanel.scss"; @import "./views/settings/_E2eAdvancedPanel.scss"; @import "./views/settings/_EmailAddresses.scss"; +@import "./views/settings/_FontScalingPanel.scss"; @import "./views/settings/_IntegrationManager.scss"; @import "./views/settings/_JoinRuleSettings.scss"; @import "./views/settings/_LayoutSwitcher.scss"; diff --git a/res/css/views/settings/_FontScalingPanel.scss b/res/css/views/settings/_FontScalingPanel.scss new file mode 100644 index 0000000000..bac1f92a4f --- /dev/null +++ b/res/css/views/settings/_FontScalingPanel.scss @@ -0,0 +1,81 @@ +/* +Copyright 2021 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. +*/ + +.mx_FontScalingPanel { + color: $primary-content; + > .mx_SettingsTab_SubHeading { + margin-bottom: 32px; + } +} + +.mx_FontScalingPanel .mx_Field { + width: 256px; +} + +.mx_FontScalingPanel_fontSlider, +.mx_FontScalingPanel_fontSlider_preview { + @mixin mx_Settings_fullWidthField; +} + +.mx_FontScalingPanel_fontSlider { + display: flex; + flex-direction: row; + align-items: center; + padding: 15px; + background: rgba($appearance-tab-border-color, 0.2); + border-radius: 10px; + font-size: 10px; + margin-top: 24px; + margin-bottom: 24px; +} + +.mx_FontScalingPanel_fontSlider_preview { + border: 1px solid $appearance-tab-border-color; + border-radius: 10px; + padding: 0 16px 9px 16px; + pointer-events: none; + display: flow-root; + + .mx_EventTile[data-layout=bubble] { + margin-top: 30px; + } + + .mx_EventTile_msgOption { + display: none; + } + + &.mx_IRCLayout { + padding-top: 9px; + } +} + +.mx_FontScalingPanel_fontSlider_smallText { + font-size: 15px; + padding-right: 20px; + padding-left: 5px; + font-weight: 500; +} + +.mx_FontScalingPanel_fontSlider_largeText { + font-size: 18px; + padding-left: 20px; + padding-right: 5px; + font-weight: 500; +} + +.mx_FontScalingPanel_customFontSizeField { + margin-left: calc($font-16px + 10px); +} diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 57c6e9b865..21082786e9 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -14,65 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_AppearanceUserSettingsTab_fontSlider, -.mx_AppearanceUserSettingsTab_fontSlider_preview { - @mixin mx_Settings_fullWidthField; -} - .mx_AppearanceUserSettingsTab .mx_Field { width: 256px; } -.mx_AppearanceUserSettingsTab_fontScaling { - color: $primary-content; -} - -.mx_AppearanceUserSettingsTab_fontSlider { - display: flex; - flex-direction: row; - align-items: center; - padding: 15px; - background: rgba($appearance-tab-border-color, 0.2); - border-radius: 10px; - font-size: 10px; - margin-top: 24px; - margin-bottom: 24px; -} - -.mx_AppearanceUserSettingsTab_fontSlider_preview { - border: 1px solid $appearance-tab-border-color; - border-radius: 10px; - padding: 0 16px 9px 16px; - pointer-events: none; - display: flow-root; - - .mx_EventTile[data-layout=bubble] { - margin-top: 30px; - } - - .mx_EventTile_msgOption { - display: none; - } - - &.mx_IRCLayout { - padding-top: 9px; - } -} - -.mx_AppearanceUserSettingsTab_fontSlider_smallText { - font-size: 15px; - padding-right: 20px; - padding-left: 5px; - font-weight: 500; -} - -.mx_AppearanceUserSettingsTab_fontSlider_largeText { - font-size: 18px; - padding-left: 20px; - padding-right: 5px; - font-weight: 500; -} - .mx_AppearanceUserSettingsTab { > .mx_SettingsTab_SubHeading { margin-bottom: 32px; @@ -151,10 +96,6 @@ limitations under the License. } } -.mx_SettingsTab_customFontSizeField { - margin-left: calc($font-16px + 10px); -} - .mx_AppearanceUserSettingsTab_Advanced { color: $primary-content; diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx new file mode 100644 index 0000000000..dfd239c38c --- /dev/null +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -0,0 +1,172 @@ +/* +Copyright 2021 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 EventTilePreview from '../elements/EventTilePreview'; +import Field from '../elements/Field'; +import React, { ChangeEvent } from 'react'; +import SettingsFlag from '../elements/SettingsFlag'; +import SettingsStore from "../../../settings/SettingsStore"; +import Slider from "../elements/Slider"; +import { FontWatcher } from "../../../settings/watchers/FontWatcher"; +import { IValidationResult, IFieldState } from '../elements/Validation'; +import { Layout } from "../../../settings/Layout"; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; +import { SettingLevel } from "../../../settings/SettingLevel"; +import { _t } from "../../../languageHandler"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +interface IProps { +} + +export interface CustomThemeMessage { + isError: boolean; + text: string; +} + +interface IState { + // String displaying the current selected fontSize. + // Needs to be string for things like '17.' without + // trailing 0s. + fontSize: string; + useCustomFontSize: boolean; + useSystemFont: boolean; + systemFont: string; + layout: Layout; + // User profile data for the message preview + userId?: string; + displayName: string; + avatarUrl: string; +} + +@replaceableComponent("views.settings.tabs.user.FontScalingPanel") +export default class FontScalingPanel extends React.Component { + private readonly MESSAGE_PREVIEW_TEXT = _t("Hey you. You're the best!"); + + private unmounted = false; + + constructor(props: IProps) { + super(props); + + this.state = { + fontSize: (SettingsStore.getValue("baseFontSize", null) + FontWatcher.SIZE_DIFF).toString(), + useCustomFontSize: SettingsStore.getValue("useCustomFontSize"), + useSystemFont: SettingsStore.getValue("useSystemFont"), + systemFont: SettingsStore.getValue("systemFont"), + layout: SettingsStore.getValue("layout"), + userId: null, + displayName: null, + avatarUrl: null, + }; + } + + async componentDidMount() { + // Fetch the current user profile for the message preview + const client = MatrixClientPeg.get(); + const userId = client.getUserId(); + const profileInfo = await client.getProfileInfo(userId); + if (this.unmounted) return; + + this.setState({ + userId, + displayName: profileInfo.displayname, + avatarUrl: profileInfo.avatar_url, + }); + } + + componentWillUnmount() { + this.unmounted = true; + } + + private onFontSizeChanged = (size: number): void => { + this.setState({ fontSize: size.toString() }); + SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF); + }; + + private onValidateFontSize = async ({ value }: Pick): Promise => { + const parsedSize = parseFloat(value); + const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; + const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; + + if (isNaN(parsedSize)) { + return { valid: false, feedback: _t("Size must be a number") }; + } + + if (!(min <= parsedSize && parsedSize <= max)) { + return { + valid: false, + feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', { min, max }), + }; + } + + SettingsStore.setValue( + "baseFontSize", + null, + SettingLevel.DEVICE, + parseInt(value, 10) - FontWatcher.SIZE_DIFF, + ); + + return { valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', { min, max }) }; + }; + + public render() { + return
+ + { _t("Font size") } + +
+
Aa
+ ""} + disabled={this.state.useCustomFontSize} + /> +
Aa
+
+ + this.setState({ useCustomFontSize: checked })} + useCheckbox={true} + /> + + ) => + this.setState({ fontSize: value.target.value }) + } + disabled={!this.state.useCustomFontSize} + className="mx_FontScalingPanel_customFontSizeField" + /> +
; + } +} diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index f2f4e34d79..3abb90d2a9 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -22,17 +22,13 @@ import { MatrixClientPeg } from '../../../../../MatrixClientPeg'; import SettingsStore from "../../../../../settings/SettingsStore"; import { enumerateThemes } from "../../../../../theme"; import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; -import Slider from "../../../elements/Slider"; import AccessibleButton from "../../../elements/AccessibleButton"; import dis from "../../../../../dispatcher/dispatcher"; -import { FontWatcher } from "../../../../../settings/watchers/FontWatcher"; import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; import { Action } from '../../../../../dispatcher/actions'; -import { IValidationResult, IFieldState } from '../../../elements/Validation'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; -import EventTilePreview from '../../../elements/EventTilePreview'; import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; @@ -42,6 +38,7 @@ import { compare } from "../../../../../utils/strings"; import LayoutSwitcher from "../../LayoutSwitcher"; import { logger } from "matrix-js-sdk/src/logger"; +import FontScalingPanel from '../../FontScalingPanel'; interface IProps { } @@ -57,13 +54,8 @@ export interface CustomThemeMessage { } interface IState extends IThemeState { - // String displaying the current selected fontSize. - // Needs to be string for things like '17.' without - // trailing 0s. - fontSize: string; customThemeUrl: string; customThemeMessage: CustomThemeMessage; - useCustomFontSize: boolean; useSystemFont: boolean; systemFont: string; showAdvanced: boolean; @@ -85,11 +77,9 @@ export default class AppearanceUserSettingsTab extends React.Component({ action: Action.RecheckTheme }); }; - private onFontSizeChanged = (size: number): void => { - this.setState({ fontSize: size.toString() }); - SettingsStore.setValue("baseFontSize", null, SettingLevel.DEVICE, size - FontWatcher.SIZE_DIFF); - }; - - private onValidateFontSize = async ({ value }: Pick): Promise => { - const parsedSize = parseFloat(value); - const min = FontWatcher.MIN_SIZE + FontWatcher.SIZE_DIFF; - const max = FontWatcher.MAX_SIZE + FontWatcher.SIZE_DIFF; - - if (isNaN(parsedSize)) { - return { valid: false, feedback: _t("Size must be a number") }; - } - - if (!(min <= parsedSize && parsedSize <= max)) { - return { - valid: false, - feedback: _t('Custom font size can only be between %(min)s pt and %(max)s pt', { min, max }), - }; - } - - SettingsStore.setValue( - "baseFontSize", - null, - SettingLevel.DEVICE, - parseInt(value, 10) - FontWatcher.SIZE_DIFF, - ); - - return { valid: true, feedback: _t('Use between %(min)s pt and %(max)s pt', { min, max }) }; - }; - private onAddCustomTheme = async (): Promise => { let currentThemes: string[] = SettingsStore.getValue("custom_themes"); if (!currentThemes) currentThemes = []; @@ -337,52 +296,6 @@ export default class AppearanceUserSettingsTab extends React.Component - - { _t("Font size") } - -
-
Aa
- ""} - disabled={this.state.useCustomFontSize} - /> -
Aa
-
- - this.setState({ useCustomFontSize: checked })} - useCheckbox={true} - /> - - this.setState({ fontSize: value.target.value })} - disabled={!this.state.useCustomFontSize} - className="mx_SettingsTab_customFontSizeField" - /> - ; - } - private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -471,7 +384,7 @@ export default class AppearanceUserSettingsTab extends React.Component { this.renderThemeSection() } { layoutSection } - { this.renderFontSection() } + { this.renderAdvancedSection() } ); diff --git a/test/components/views/settings/FontScalingPanel-test.tsx b/test/components/views/settings/FontScalingPanel-test.tsx new file mode 100644 index 0000000000..76da094b7c --- /dev/null +++ b/test/components/views/settings/FontScalingPanel-test.tsx @@ -0,0 +1,38 @@ +/* +Copyright 2021 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 React from 'react'; +import { mount } from "enzyme"; + +import '../../../skinned-sdk'; +import * as TestUtils from "../../../test-utils"; +import _FontScalingPanel from '../../../../src/components/views/settings/FontScalingPanel'; + +const FontScalingPanel = TestUtils.wrapInMatrixClientContext(_FontScalingPanel); + +import * as randomstring from "matrix-js-sdk/src/randomstring"; +// @ts-expect-error: override random function to make results predictable +randomstring.randomString = () => "abdefghi"; + +describe('FontScalingPanel', () => { + it('renders the font scaling UI', () => { + TestUtils.stubClient(); + const wrapper = mount( + , + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap new file mode 100644 index 0000000000..2174b4f289 --- /dev/null +++ b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap @@ -0,0 +1,307 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FontScalingPanel renders the font scaling UI 1`] = ` + + +
+ + Font size + + +
+ +
+
+
+ +
+ +
+
+ Aa +
+ +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + + + +
+
+
+
+ + +
+
+
+ +
+ Aa +
+
+ + + + + + + + + +
+ + +
+
+
+ + +`;