From dad1d3f13148f0a31b36bc33016b88823b472c6b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 18 Oct 2021 13:47:42 -0600 Subject: [PATCH 001/100] Fix runtime react errors for various parts of the app These are just the ones that were causing console flooding on reload in development. * Elements in a list need a `key` * `super()` needs to be supplied with the same props as the parent * `
` (AccessibleButton) cannot be a descendant of `

` - this was a problem in the NewRoomIntro "Add topic" button * `label` is a non-boolean property and cannot receive "false" --- src/components/views/elements/PersistentApp.tsx | 10 +++++++--- src/components/views/rooms/MessageComposer.tsx | 9 ++++++--- src/components/views/rooms/NewRoomIntro.tsx | 6 +++++- src/components/views/rooms/RoomHeader.tsx | 5 +++++ 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/views/elements/PersistentApp.tsx b/src/components/views/elements/PersistentApp.tsx index 8d0751cc1d..d80a00584c 100644 --- a/src/components/views/elements/PersistentApp.tsx +++ b/src/components/views/elements/PersistentApp.tsx @@ -25,17 +25,21 @@ import { EventSubscription } from 'fbemitter'; import AppTile from "./AppTile"; import { Room } from "matrix-js-sdk/src/models/room"; +interface IProps { + // none +} + interface IState { roomId: string; persistentWidgetId: string; } @replaceableComponent("views.elements.PersistentApp") -export default class PersistentApp extends React.Component<{}, IState> { +export default class PersistentApp extends React.Component { private roomStoreToken: EventSubscription; - constructor() { - super({}); + constructor(props: IProps) { + super(props); this.state = { roomId: RoomViewStore.getRoomId(), diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 0d0b7fa441..f79fb372dc 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -116,7 +116,7 @@ const EmojiButton: React.FC = ({ addEmoji, menuPosition, narr className={className} onClick={openMenu} title={!narrowMode && _t('Emoji picker')} - label={narrowMode && _t("Add emoji")} + label={(narrowMode && _t("Add emoji")) || null} /> { contextMenu } @@ -485,13 +485,14 @@ export default class MessageComposer extends React.Component { className="mx_MessageComposer_button mx_MessageComposer_stickers" onClick={() => this.showStickers(!this.state.showStickers)} title={title} - label={this.state.narrowMode && _t("Send a sticker")} + label={(this.state.narrowMode && _t("Send a sticker")) || null} />, ); } if (!this.state.haveRecording && !this.state.narrowMode) { buttons.push( this.voiceRecordingButton.current?.onRecordStartEndClick()} title={_t("Send voice message")} @@ -615,7 +616,9 @@ export default class MessageComposer extends React.Component { room={this.props.room} showStickers={this.state.showStickers} setShowStickers={this.showStickers} - menuPosition={menuPosition} />, + menuPosition={menuPosition} + key="stickers" + />, ); const showSendButton = !this.state.isComposerEmpty || this.state.haveRecording; diff --git a/src/components/views/rooms/NewRoomIntro.tsx b/src/components/views/rooms/NewRoomIntro.tsx index fbaccfd6b5..3d92a9cced 100644 --- a/src/components/views/rooms/NewRoomIntro.tsx +++ b/src/components/views/rooms/NewRoomIntro.tsx @@ -106,7 +106,11 @@ const NewRoomIntro = () => { topicText = _t("Topic: %(topic)s ", { topic }); } else if (canAddTopic) { topicText = _t("Add a topic to help people know what it is about.", {}, { - a: sub => { sub }, + a: sub => { sub }, }); } diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index e3b4804ae6..a1cd079ccb 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -162,12 +162,14 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton" onClick={() => this.props.onCallPlaced(PlaceCallType.Voice)} title={_t("Voice call")} + key="voice" />; const videoCallButton = ) => ev.shiftKey ? this.displayInfoDialogAboutScreensharing() : this.props.onCallPlaced(PlaceCallType.Video)} title={_t("Video call")} + key="video" />; buttons.push(voiceCallButton, videoCallButton); } @@ -177,6 +179,7 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_forgetButton" onClick={this.props.onForgetClick} title={_t("Forget room")} + key="forget" />; buttons.push(forgetButton); } @@ -188,6 +191,7 @@ export default class RoomHeader extends React.Component { })} onClick={this.props.onAppsClick} title={this.props.appsShown ? _t("Hide Widgets") : _t("Show Widgets")} + key="apps" />; buttons.push(appsButton); } @@ -197,6 +201,7 @@ export default class RoomHeader extends React.Component { className="mx_RoomHeader_button mx_RoomHeader_searchButton" onClick={this.props.onSearchClick} title={_t("Search")} + key="search" />; buttons.push(searchButton); } From 7babdd9c9d0ccc7488dfe7c6b00a3d8771042c6f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 18 Oct 2021 15:40:06 -0600 Subject: [PATCH 002/100] Ternary --- src/components/views/rooms/MessageComposer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index f79fb372dc..fc88eae9e6 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -116,7 +116,7 @@ const EmojiButton: React.FC = ({ addEmoji, menuPosition, narr className={className} onClick={openMenu} title={!narrowMode && _t('Emoji picker')} - label={(narrowMode && _t("Add emoji")) || null} + label={narrowMode ? _t("Add emoji") : null} /> { contextMenu } @@ -485,7 +485,7 @@ export default class MessageComposer extends React.Component { className="mx_MessageComposer_button mx_MessageComposer_stickers" onClick={() => this.showStickers(!this.state.showStickers)} title={title} - label={(this.state.narrowMode && _t("Send a sticker")) || null} + label={this.state.narrowMode ? _t("Send a sticker") : null} />, ); } From e8b998c19c8659a261151d33d0e73f43093919c1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 19 Oct 2021 09:43:34 +0100 Subject: [PATCH 003/100] Use prettier hsName during 3pid registration where possible --- src/components/structures/MatrixChat.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index a9e7876d90..f0a7e634b0 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -872,6 +872,15 @@ export default class MatrixChat extends React.PureComponent { params.hs_url, params.is_url, ); + // If the hs url matches then take the hs name we know locally as it is likely prettier + const defaultConfig = SdkConfig.get()["validated_server_config"] as ValidatedServerConfig; + if (defaultConfig && defaultConfig.hsUrl === newState.serverConfig.hsUrl) { + newState.serverConfig.hsName = defaultConfig.hsName; + newState.serverConfig.hsNameIsDifferent = defaultConfig.hsNameIsDifferent; + newState.serverConfig.isDefault = defaultConfig.isDefault; + newState.serverConfig.isNameResolvable = defaultConfig.isNameResolvable; + } + newState.register_client_secret = params.client_secret; newState.register_session_id = params.session_id; newState.register_id_sid = params.sid; From 4ad32b16ea09633cae5699a728e6adb64138f37b Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 19 Oct 2021 14:50:09 +0100 Subject: [PATCH 004/100] 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 +
+
+ + + + + + + + + +
+ + +
+
+
+ + +`; From 493de82774fd82099e31ccc12cd099ec80d4ce0d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 19 Oct 2021 11:26:31 +0100 Subject: [PATCH 005/100] Upgrade yarn dependencies --- yarn.lock | 150 +++++++++++++++++++++++++++++------------------------- 1 file changed, 80 insertions(+), 70 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0404e9b6b7..5c739e58c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,7 +54,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== -"@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.7.5": +"@babel/core@>=7.9.0", "@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.7.5": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.8.tgz#195b9f2bffe995d2c6c159e72fe525b4114e8c10" integrity sha512-3UG9dsxvYBMYwRv+gS41WKHno4K60/9GPy1CJaH6xy3Elq8CTtvtjT5R5jmNhXfCYLX2mTw+7/aq5ak/gOE0og== @@ -307,7 +307,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": +"@babel/parser@^7.1.0", "@babel/parser@^7.12.11", "@babel/parser@^7.13.16", "@babel/parser@^7.14.7", "@babel/parser@^7.15.4", "@babel/parser@^7.15.8": version "7.15.8" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.8.tgz#7bacdcbe71bdc3ff936d510c15dcea7cf0b99016" integrity sha512-BRYa3wcQnjS/nqI8Ac94pYYpJfojHVvVXJ97+IDCImX4Jc8W8Xv1+47enbruk+q1etOpsQNwnfFcNGw+gtPGxA== @@ -1757,14 +1757,14 @@ integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA== "@types/node@*": - version "16.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" - integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== + version "16.11.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97" + integrity sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA== "@types/node@^14.14.22": - version "14.17.21" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.21.tgz#6359d8cf73481e312a43886fa50afc70ce5592c6" - integrity sha512-zv8ukKci1mrILYiQOwGSV4FpkZhyxQtuFWGya2GujWg+zVAeRQ4qbaMmWp9vb9889CFA8JECH7lkwCL6Ygg8kA== + version "14.17.27" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.27.tgz#5054610d37bb5f6e21342d0e6d24c494231f3b85" + integrity sha512-94+Ahf9IcaDuJTle/2b+wzvjmutxXAEXU6O81JHblYXUg2BDG+dnBy7VxIPHKAyEEDHzCMQydTJuWvrE+Aanzw== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -2083,11 +2083,6 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.21.3" -ansi-regex@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - ansi-regex@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" @@ -2309,14 +2304,14 @@ babel-plugin-dynamic-import-node@^2.3.3: object.assign "^4.1.0" babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" "@istanbuljs/load-nyc-config" "^1.0.0" "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" babel-plugin-jest-hoist@^26.6.2: @@ -2503,15 +2498,15 @@ browser-request@^0.3.3: integrity sha1-ns5bWsqJopkyJC4Yv5M975h2zBc= browserslist@^4.12.0, browserslist@^4.16.6, browserslist@^4.17.3: - version "4.17.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.3.tgz#2844cd6eebe14d12384b0122d217550160d2d624" - integrity sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ== + version "4.17.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.4.tgz#72e2508af2a403aec0a49847ef31bd823c57ead4" + integrity sha512-Zg7RpbZpIJRW3am9Lyckue7PLytvVxxhJj1CaJVlCWENsGEAOlnlt8X0ZxGRPp7Bt9o8tIRM5SEXy4BCPMJjLQ== dependencies: - caniuse-lite "^1.0.30001264" - electron-to-chromium "^1.3.857" + caniuse-lite "^1.0.30001265" + electron-to-chromium "^1.3.867" escalade "^3.1.1" - node-releases "^1.1.77" - picocolors "^0.2.1" + node-releases "^2.0.0" + picocolors "^1.0.0" bs58@^4.0.1: version "4.0.1" @@ -2605,10 +2600,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001264: - version "1.0.30001265" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" - integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001265: + version "1.0.30001269" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz#3a71bee03df627364418f9fd31adfc7aa1cc2d56" + integrity sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w== capture-exit@^2.0.0: version "2.0.0" @@ -2732,15 +2727,14 @@ classnames@*, classnames@^2.2.6: integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== cli-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.0.tgz#11ecfb58a79278cf6035a60c54e338f9d837897c" - integrity sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A== + version "2.0.1" + resolved "https://registry.yarnpkg.com/cli-color/-/cli-color-2.0.1.tgz#93e3491308691f1e46beb78b63d0fb2585e42ba6" + integrity sha512-eBbxZF6fqPUNnf7CLAFOersUnyYzv83tHFLSlts+OAHsNendaqv2tHCq+/MO+b3Y+9JeoUlIvobyxG/Z8GNeOg== dependencies: - ansi-regex "^2.1.1" d "^1.0.1" - es5-ext "^0.10.51" + es5-ext "^0.10.53" es6-iterator "^2.0.3" - memoizee "^0.4.14" + memoizee "^0.4.15" timers-ext "^0.1.7" cliui@^5.0.0: @@ -2903,9 +2897,9 @@ copy-descriptor@^0.1.0: integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= core-js-compat@^3.16.0, core-js-compat@^3.16.2: - version "3.18.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.18.2.tgz#e40c266fbd613948dd8d2d2156345da8ac03c142" - integrity sha512-25VJYCJtGjZwLguj7d66oiHfmnVw3TMOZ0zV8DyMJp/aeQ3OjR519iOOeck08HMyVVRAqXxafc2Hl+5QstJrsQ== + version "3.18.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.18.3.tgz#e0e7e87abc55efb547e7fa19169e45fa9df27a67" + integrity sha512-4zP6/y0a2RTHN5bRGT7PTq9lVt3WzvffTNjqnTKsXhkAYNDTkdCLOIfAdOLcQ/7TDdyRj3c+NeHe1NmF1eDScw== dependencies: browserslist "^4.17.3" semver "7.0.0" @@ -3165,9 +3159,9 @@ detect-node-es@^1.1.0: integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== diff-dom@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-4.2.2.tgz#49a121fe1614675b4f28ed50df9547bb0c3c1d52" - integrity sha512-qO0WsnnMF6Wc5MEm0j4LkwvJWs4Mpkj6pyXWMtcjGJPHWDNcnQY8ijYoSrsZuArBmlHLN9Dqr4de/3ZlbjpRVw== + version "4.2.3" + resolved "https://registry.yarnpkg.com/diff-dom/-/diff-dom-4.2.3.tgz#c6234b49c1b49e41601d2f08dbb26cd57842de45" + integrity sha512-8OZPIbTWVhkQVlUlsb+VuMEMpTpKKhO5FTwds2bYVIaBiPNJCG1YW5qXUyLWMux5gC2UGyYXjtX05SPivnGMCw== diff-match-patch@^1.0.5: version "1.0.5" @@ -3291,10 +3285,10 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.3.857: - version "1.3.866" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.866.tgz#d446338f5ad6948b27a50739760e7b0b5cc5032f" - integrity sha512-iYze6TpDXWxk+sfcpUUdTs6Pv/3kG45Pnjer2DxEeFw0N08bZeNLuz97s2lMgy8yObon48o0WHY2Bkg3xuAPOA== +electron-to-chromium@^1.3.867: + version "1.3.872" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.872.tgz#2311a82f344d828bab6904818adc4afb57b35369" + integrity sha512-qG96atLFY0agKyEETiBFNhpRLSXGSXOBuhXWpbkYqrLKKASpRyRBUtfkn0ZjIf/yXfA7FA4nScVOMpXSHFlUCQ== emittery@^0.7.1: version "0.7.2" @@ -3468,7 +3462,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.51, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -4661,9 +4655,9 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-core-module@^2.2.0, is-core-module@^2.5.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== + version "2.8.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" @@ -5012,11 +5006,11 @@ isstream@~0.1.2: integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= istanbul-lib-coverage@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.2.tgz#36786d4d82aad2ea5911007e255e2da6b5f80d86" - integrity sha512-o5+eTUYzCJ11/+JhW5/FUCdfsdoYVdQ/8I/OveE2XsjehYn5DdeSnNQAbjYaO8gQ6hvGTN6GM6ddQqpTVG5j8g== + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: +istanbul-lib-instrument@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== @@ -5026,6 +5020,17 @@ istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" +istanbul-lib-instrument@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.0.4.tgz#e976f2aa66ebc6737f236d3ab05b76e36f885c80" + integrity sha512-W6jJF9rLGEISGoCyXRqa/JCGQGmmxPO10TMu7izaUTynxvBvTjqzAIIGCK9USBmIbQAaSWD6XJPrM9Pv5INknw== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + istanbul-lib-report@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" @@ -5045,9 +5050,9 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.4.tgz#5c38ce8136edf484c0fcfbf7514aafb0363ed1db" - integrity sha512-bFjUnc95rHjdCR63WMHUS7yfJJh8T9IPSWavvR02hhjVwezWALZ5axF9EqjmwZHpXqkzbgAMP8DmAtiyNxrdrQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.5.tgz#a2580107e71279ea6d661ddede929ffc6d693384" + integrity sha512-5+19PlhnGabNWB7kOFnuxT8H3T/iIyQzIbQMxXsURmmvKg86P2sbkrGOT77VnHw0Qr0gc2XzRaRfMZYYbSQCJQ== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" @@ -5866,7 +5871,7 @@ mathml-tag-names@^2.1.3: "matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "14.0.1" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e7b41fadd0d6eda7423a369c99ec7b94afd48d5d" + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/94ff0ea4cd9dfd37400b079c8da08b8ecd0f7c6f" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" @@ -5946,7 +5951,7 @@ memoize-one@^5.1.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== -memoizee@^0.4.14: +memoizee@^0.4.15: version "0.4.15" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.15.tgz#e6f3d2da863f318d02225391829a6c5956555b72" integrity sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ== @@ -6097,9 +6102,9 @@ ms@2.1.2: integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== nanoid@^3.1.28: - version "3.1.29" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.29.tgz#214fb2d7a33e1a5bef4757b779dfaeb6a4e5aeb4" - integrity sha512-dW2pUSGZ8ZnCFIlBIA31SV8huOGCHb6OwzVCc7A69rb/a+SgPBwfmLvK5TKQ3INPbRkcI8a/Owo0XbiTNH19wg== + version "3.1.30" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362" + integrity sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ== nanomatch@^1.2.9: version "1.2.13" @@ -6190,10 +6195,10 @@ node-notifier@^8.0.0: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.77: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== +node-releases@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400" + integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" @@ -6413,9 +6418,9 @@ optionator@^0.9.1: word-wrap "^1.2.3" opus-recorder@^8.0.3: - version "8.0.4" - resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.4.tgz#c4cdbb8bb94d17aa406934b58dcf9caab6c79b09" - integrity sha512-nWwLH5BySgNDHdpkOMV+igl9iyS99g60ap/0LycIgbSXykZvUpweuWCgAl3mTKSL0773yvKohlO5dOv5RQqG/Q== + version "8.0.5" + resolved "https://registry.yarnpkg.com/opus-recorder/-/opus-recorder-8.0.5.tgz#06d3e32e15da57ebc3f57e41b93033475fcb4e3e" + integrity sha512-tBRXc9Btds7i3bVfA7d5rekAlyOcfsivt5vSIXHxRV1Oa+s6iXFW8omZ0Lm3ABWotVcEyKt96iIIUcgbV07YOw== p-each-series@^2.1.0: version "2.2.0" @@ -6575,6 +6580,11 @@ picocolors@^0.2.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -7370,9 +7380,9 @@ sane@^4.0.3: walker "~1.0.5" sanitize-html@^2.3.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.1.tgz#f49998dc54c8180153940440d3a7294b09e4258a" - integrity sha512-hUITPitQk+eFNLtr4dEkaaiAJndG2YE87IOpcfBSL1XdklWgwcNDJdr9Ppe8QKL/C3jFt1xH/Mbj20e0GZQOfg== + version "2.5.2" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.2.tgz#fd7892340e7fc9afd5722200929258808c578784" + integrity sha512-sJ1rO2YixFIqs2kIcEUb6PTrCjvz8DMq1XqWWuy0kjgjrn58GNLK1DKSIRybFZDO1WNgsEgD+WiEzTEYS8xEug== dependencies: deepmerge "^4.2.2" escape-string-regexp "^4.0.0" From 79edceca8943e3fe21526df840c2e72c01a6dac6 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 19 Oct 2021 15:23:12 +0100 Subject: [PATCH 006/100] Remove unused fields --- src/components/views/settings/FontScalingPanel.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/views/settings/FontScalingPanel.tsx b/src/components/views/settings/FontScalingPanel.tsx index dfd239c38c..aabfc1c9a4 100644 --- a/src/components/views/settings/FontScalingPanel.tsx +++ b/src/components/views/settings/FontScalingPanel.tsx @@ -31,19 +31,12 @@ 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; @@ -63,8 +56,6 @@ export default class FontScalingPanel extends React.Component { 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, From d70a706c15d86ede41b7d97d7d48331fa718cd30 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 19 Oct 2021 15:25:33 +0100 Subject: [PATCH 007/100] i18n updates --- src/i18n/strings/en_EN.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 24ac3d162d..9fab4afd42 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1158,6 +1158,10 @@ "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.": "%(brand)s is missing some components required for securely caching encrypted messages locally. If you'd like to experiment with this feature, build a custom %(brand)s Desktop with search components added.", "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.": "%(brand)s can't securely cache encrypted messages locally while running in a web browser. Use %(brand)s Desktop for encrypted messages to appear in search results.", "Message search initialisation failed": "Message search initialisation failed", + "Hey you. You're the best!": "Hey you. You're the best!", + "Size must be a number": "Size must be a number", + "Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt", + "Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt", "Connecting to integration manager...": "Connecting to integration manager...", "Cannot connect to integration manager": "Cannot connect to integration manager", "The integration manager is offline or it cannot reach your homeserver.": "The integration manager is offline or it cannot reach your homeserver.", @@ -1289,10 +1293,6 @@ "Downloading update...": "Downloading update...", "New version available. Update now.": "New version available. Update now.", "Check for update": "Check for update", - "Hey you. You're the best!": "Hey you. You're the best!", - "Size must be a number": "Size must be a number", - "Custom font size can only be between %(min)s pt and %(max)s pt": "Custom font size can only be between %(min)s pt and %(max)s pt", - "Use between %(min)s pt and %(max)s pt": "Use between %(min)s pt and %(max)s pt", "Invalid theme schema.": "Invalid theme schema.", "Error downloading theme information.": "Error downloading theme information.", "Theme added!": "Theme added!", From 2582f6df238f0f64524027ccad84b0f951778686 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Tue, 19 Oct 2021 15:45:11 +0100 Subject: [PATCH 008/100] Replace manual mock with jest.mock --- .../views/settings/FontScalingPanel-test.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/components/views/settings/FontScalingPanel-test.tsx b/test/components/views/settings/FontScalingPanel-test.tsx index 76da094b7c..3c3a68e274 100644 --- a/test/components/views/settings/FontScalingPanel-test.tsx +++ b/test/components/views/settings/FontScalingPanel-test.tsx @@ -23,9 +23,15 @@ import _FontScalingPanel from '../../../../src/components/views/settings/FontSca 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"; +// Fake random strings to give a predictable snapshot +jest.mock( + 'matrix-js-sdk/src/randomstring', + () => { + return { + randomString: () => "abdefghi", + }; + }, +); describe('FontScalingPanel', () => { it('renders the font scaling UI', () => { From 694ec946e2ec0f78cc5f3259ed76004f17ae2b69 Mon Sep 17 00:00:00 2001 From: Germain Date: Tue, 19 Oct 2021 16:05:34 +0100 Subject: [PATCH 009/100] Allow quote-reply in thread view element-web (#6959) --- src/components/structures/RoomView.tsx | 5 +- src/components/structures/ThreadPanel.tsx | 17 +----- src/components/structures/ThreadView.tsx | 10 +++- .../views/messages/MessageActionBar.tsx | 10 ++-- src/components/views/rooms/EventTile.tsx | 21 ++++++-- .../views/rooms/MessageComposer.tsx | 13 ++--- src/components/views/rooms/ReplyPreview.tsx | 52 ++++--------------- .../views/rooms/SendMessageComposer.tsx | 8 ++- src/contexts/RoomContext.ts | 10 ++-- src/stores/RoomViewStore.tsx | 24 +++++---- .../views/rooms/SendMessageComposer-test.tsx | 2 + 11 files changed, 84 insertions(+), 88 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index db7d7acd90..c2e071b836 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -779,7 +779,10 @@ export class RoomView extends React.Component { }); break; case 'reply_to_event': - if (this.state.searchResults && payload.event.getRoomId() === this.state.roomId && !this.unmounted) { + if (this.state.searchResults + && payload.event.getRoomId() === this.state.roomId + && !this.unmounted + && payload.context === TimelineRenderingType.Room) { this.onCancelSearchClick(); } break; diff --git a/src/components/structures/ThreadPanel.tsx b/src/components/structures/ThreadPanel.tsx index 40e9479251..2c3cba683b 100644 --- a/src/components/structures/ThreadPanel.tsx +++ b/src/components/structures/ThreadPanel.tsx @@ -15,7 +15,6 @@ limitations under the License. */ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { Thread, ThreadEvent } from 'matrix-js-sdk/src/models/thread'; import { EventTimelineSet } from 'matrix-js-sdk/src/models/event-timeline-set'; import { Room } from 'matrix-js-sdk/src/models/room'; @@ -24,7 +23,6 @@ import BaseCard from "../views/right_panel/BaseCard"; import { RightPanelPhases } from "../../stores/RightPanelStorePhases"; import ResizeNotifier from '../../utils/ResizeNotifier'; -import EventTile, { TileShape } from '../views/rooms/EventTile'; import MatrixClientContext from '../../contexts/MatrixClientContext'; import { _t } from '../../languageHandler'; import { ContextMenuButton } from '../../accessibility/context_menu/ContextMenuButton'; @@ -34,6 +32,7 @@ import TimelinePanel from './TimelinePanel'; import { Layout } from '../../settings/Layout'; import { useEventEmitter } from '../../hooks/useEventEmitter'; import AccessibleButton from '../views/elements/AccessibleButton'; +import { TileShape } from '../views/rooms/EventTile'; interface IProps { roomId: string; @@ -41,18 +40,6 @@ interface IProps { resizeNotifier: ResizeNotifier; } -export const ThreadPanelItem: React.FC<{ event: MatrixEvent }> = ({ event }) => { - return ; -}; - export enum ThreadFilterType { "My", "All" @@ -230,7 +217,7 @@ const ThreadPanel: React.FC = ({ roomId, onClose }) => { showReactions={true} className="mx_RoomView_messagePanel mx_GroupLayout" membersLoaded={true} - tileShape={TileShape.ThreadPanel} + tileShape={TileShape.Thread} /> diff --git a/src/components/structures/ThreadView.tsx b/src/components/structures/ThreadView.tsx index 7bd6415cd3..b0851665a7 100644 --- a/src/components/structures/ThreadView.tsx +++ b/src/components/structures/ThreadView.tsx @@ -50,6 +50,7 @@ interface IProps { interface IState { thread?: Thread; editState?: EditorStateTransfer; + replyToEvent?: MatrixEvent; } @replaceableComponent("structures.ThreadView") @@ -114,6 +115,13 @@ export default class ThreadView extends React.Component { }); break; } + case 'reply_to_event': + if (payload.context === TimelineRenderingType.Thread) { + this.setState({ + replyToEvent: payload.event, + }); + } + break; default: break; } @@ -199,7 +207,7 @@ export default class ThreadView extends React.Component { rel_type: RelationType.Thread, event_id: this.state.thread.id, }} - showReplyPreview={false} + replyToEvent={this.state.replyToEvent} permalinkCreator={this.props.permalinkCreator} e2eStatus={this.props.e2eStatus} compact={true} diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index dffe8be7a6..56ae08453c 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -63,7 +63,7 @@ const OptionsButton: React.FC = let contextMenu; if (menuDisplayed) { const tile = getTile && getTile(); - const replyThread = getReplyChain && getReplyChain(); + const replyChain = getReplyChain && getReplyChain(); const buttonRect = button.current.getBoundingClientRect(); contextMenu = = mxEvent={mxEvent} permalinkCreator={permalinkCreator} eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined} - collapseReplyChain={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined} + collapseReplyChain={replyChain && replyChain.canCollapse() ? replyChain.collapse : undefined} onFinished={closeMenu} />; } @@ -191,6 +191,7 @@ export default class MessageActionBar extends React.PureComponent - { SettingsStore.getValue("feature_thread") && ( + { (SettingsStore.getValue("feature_thread") + && this.context.timelineRenderingType !== TimelineRenderingType.Thread) && ( { private isListeningForReceipts: boolean; // TODO: Types private tile = React.createRef(); - private replyThread = React.createRef(); + private replyChain = React.createRef(); public readonly ref = createRef(); @@ -933,7 +933,7 @@ export default class EventTile extends React.Component { // TODO: Types getTile: () => any | null = () => this.tile.current; - getReplyChain = () => this.replyThread.current; + getReplyChain = () => this.replyChain.current; getReactions = () => { if ( @@ -1214,12 +1214,26 @@ export default class EventTile extends React.Component { ]); } case TileShape.Thread: { + const thread = haveTileForEvent(this.props.mxEvent) && + ReplyChain.hasReply(this.props.mxEvent) ? ( + ) : null; const room = this.context.getRoom(this.props.mxEvent.getRoomId()); return React.createElement(this.props.as || "li", { "className": classes, "aria-live": ariaLive, "aria-atomic": true, "data-scroll-tokens": scrollToken, + "data-has-reply": !!thread, }, [
@@ -1235,6 +1249,7 @@ export default class EventTile extends React.Component {
,
+ { thread } { { private ref: React.RefObject = createRef(); private instanceId: number; + public static contextType = RoomContext; + static defaultProps = { - showReplyPreview: true, compact: false, }; @@ -294,7 +295,7 @@ export default class MessageComposer extends React.Component { }; private onAction = (payload: ActionPayload) => { - if (payload.action === 'reply_to_event') { + if (payload.action === 'reply_to_event' && payload.context === this.context.timelineRenderingType) { // add a timeout for the reply preview to be rendered, so // that the ScrollPanel listening to the resizeNotifier can // correctly measure it's new height and scroll down to keep @@ -633,9 +634,9 @@ export default class MessageComposer extends React.Component {
{ recordingTooltip }
- { this.props.showReplyPreview && ( - - ) } +
{ controls } { this.renderButtons(menuPosition) } diff --git a/src/components/views/rooms/ReplyPreview.tsx b/src/components/views/rooms/ReplyPreview.tsx index 41b3d2460c..a1dfddfe19 100644 --- a/src/components/views/rooms/ReplyPreview.tsx +++ b/src/components/views/rooms/ReplyPreview.tsx @@ -17,63 +17,31 @@ limitations under the License. import React from 'react'; import dis from '../../../dispatcher/dispatcher'; import { _t } from '../../../languageHandler'; -import RoomViewStore from '../../../stores/RoomViewStore'; import { RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import ReplyTile from './ReplyTile'; import { MatrixEvent } from 'matrix-js-sdk/src/models/event'; -import { EventSubscription } from 'fbemitter'; +import RoomContext, { TimelineRenderingType } from '../../../contexts/RoomContext'; -function cancelQuoting() { +function cancelQuoting(context: TimelineRenderingType) { dis.dispatch({ action: 'reply_to_event', event: null, + context, }); } interface IProps { permalinkCreator: RoomPermalinkCreator; -} - -interface IState { - event: MatrixEvent; + replyToEvent: MatrixEvent; } @replaceableComponent("views.rooms.ReplyPreview") -export default class ReplyPreview extends React.Component { - private unmounted = false; - private readonly roomStoreToken: EventSubscription; +export default class ReplyPreview extends React.Component { + public static contextType = RoomContext; - constructor(props) { - super(props); - - this.state = { - event: RoomViewStore.getQuotingEvent(), - }; - - this.roomStoreToken = RoomViewStore.addListener(this.onRoomViewStoreUpdate); - } - - componentWillUnmount() { - this.unmounted = true; - - // Remove RoomStore listener - if (this.roomStoreToken) { - this.roomStoreToken.remove(); - } - } - - private onRoomViewStoreUpdate = (): void => { - if (this.unmounted) return; - - const event = RoomViewStore.getQuotingEvent(); - if (this.state.event !== event) { - this.setState({ event }); - } - }; - - render() { - if (!this.state.event) return null; + public render(): JSX.Element { + if (!this.props.replyToEvent) return null; return
@@ -86,13 +54,13 @@ export default class ReplyPreview extends React.Component { src={require("../../../../res/img/cancel.svg")} width="18" height="18" - onClick={cancelQuoting} + onClick={() => cancelQuoting(this.context.timelineRenderingType)} />
diff --git a/src/components/views/rooms/SendMessageComposer.tsx b/src/components/views/rooms/SendMessageComposer.tsx index 12458d201f..c32a31f249 100644 --- a/src/components/views/rooms/SendMessageComposer.tsx +++ b/src/components/views/rooms/SendMessageComposer.tsx @@ -238,6 +238,7 @@ export class SendMessageComposer extends React.Component({ diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index b949ccabcf..1a44b4fb32 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -32,6 +32,7 @@ import { retry } from "../utils/promise"; import CountlyAnalytics from "../CountlyAnalytics"; import { logger } from "matrix-js-sdk/src/logger"; +import { TimelineRenderingType } from "../contexts/RoomContext"; const NUM_JOIN_RETRY = 5; @@ -153,16 +154,19 @@ class RoomViewStore extends Store { case 'reply_to_event': // If currently viewed room does not match the room in which we wish to reply then change rooms // this can happen when performing a search across all rooms - if (payload.event && payload.event.getRoomId() !== this.state.roomId) { - dis.dispatch({ - action: 'view_room', - room_id: payload.event.getRoomId(), - replyingToEvent: payload.event, - }); - } else { - this.setState({ - replyingToEvent: payload.event, - }); + if (payload.context === TimelineRenderingType.Room) { + if (payload.event + && payload.event.getRoomId() !== this.state.roomId) { + dis.dispatch({ + action: 'view_room', + room_id: payload.event.getRoomId(), + replyingToEvent: payload.event, + }); + } else { + this.setState({ + replyingToEvent: payload.event, + }); + } } break; case 'open_room_settings': { diff --git a/test/components/views/rooms/SendMessageComposer-test.tsx b/test/components/views/rooms/SendMessageComposer-test.tsx index e6bac2b96e..ec4894719e 100644 --- a/test/components/views/rooms/SendMessageComposer-test.tsx +++ b/test/components/views/rooms/SendMessageComposer-test.tsx @@ -216,6 +216,7 @@ describe('', () => { expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: mockEvent, + context: TimelineRenderingType.Room, }); // now try with localStorage wiped out @@ -277,6 +278,7 @@ describe('', () => { expect(spyDispatcher).toHaveBeenCalledWith({ action: "reply_to_event", event: null, + context: TimelineRenderingType.Room, }); expect(wrapper.text()).toBe(""); From 974f45930c4f8d3e6576ed5e3b46d3413e84a7de Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 19 Oct 2021 16:11:53 +0100 Subject: [PATCH 010/100] Simplify Space Panel notification badge layout (#6977) --- res/css/structures/_SpacePanel.scss | 70 +++++++------------ .../views/spaces/SpaceTreeLevel.tsx | 6 +- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index c9e88d4342..d060696147 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -227,7 +227,7 @@ $activeBorderColor: $secondary-content; height: 20px; margin-top: auto; margin-bottom: auto; - display: none; + visibility: hidden; position: relative; &::before { @@ -246,67 +246,45 @@ $activeBorderColor: $secondary-content; } } + .mx_SpaceButton_avatarWrapper { + position: relative; + } + .mx_SpacePanel_badgeContainer { // Create a flexbox to make aligning dot badges easier display: flex; align-items: center; + position: absolute; + right: -3px; + top: -3px; .mx_NotificationBadge { margin: 0 2px; // centering + background-clip: padding-box; } .mx_NotificationBadge_dot { // make the smaller dot occupy the same width for centering - margin: 0 7px; + margin: 0 -1px 0 0; + border: 3px solid $groupFilterPanel-bg-color; + } + + .mx_NotificationBadge_2char, + .mx_NotificationBadge_3char { + margin: -5px -5px 0 0; + border: 2px solid $groupFilterPanel-bg-color; } } - &.collapsed { - .mx_SpaceButton { - .mx_SpacePanel_badgeContainer { - position: absolute; - right: 0; - top: 0; - - .mx_NotificationBadge { - background-clip: padding-box; - } - - .mx_NotificationBadge_dot { - margin: 0 -1px 0 0; - border: 3px solid $groupFilterPanel-bg-color; - } - - .mx_NotificationBadge_2char, - .mx_NotificationBadge_3char { - margin: -5px -5px 0 0; - border: 2px solid $groupFilterPanel-bg-color; - } - } - - &.mx_SpaceButton_active .mx_SpacePanel_badgeContainer { - // when we draw the selection border we move the relative bounds of our parent - // so update our position within the bounds of the parent to maintain position overall - right: -3px; - top: -3px; - } - } + .mx_SpaceButton_narrow .mx_SpaceButton_menuButton { + display: none; } - &:not(.collapsed) { - .mx_SpaceButton:hover, - .mx_SpaceButton:focus-within, - .mx_SpaceButton_hasMenuOpen { - &:not(.mx_SpaceButton_invite) { - // Hide the badge container on hover because it'll be a menu button - .mx_SpacePanel_badgeContainer { - display: none; - } - - .mx_SpaceButton_menuButton { - display: block; - } - } + .mx_SpaceButton:hover, + .mx_SpaceButton:focus-within, + .mx_SpaceButton_hasMenuOpen { + &:not(.mx_SpaceButton_invite) .mx_SpaceButton_menuButton { + visibility: visible; } } diff --git a/src/components/views/spaces/SpaceTreeLevel.tsx b/src/components/views/spaces/SpaceTreeLevel.tsx index df6c4c8149..ba2af3b857 100644 --- a/src/components/views/spaces/SpaceTreeLevel.tsx +++ b/src/components/views/spaces/SpaceTreeLevel.tsx @@ -125,9 +125,11 @@ export const SpaceButton: React.FC = ({ > { children }
- { avatar } +
+ { avatar } + { notifBadge } +
{ !isNarrow && { label } } - { notifBadge } { ContextMenuComponent && Date: Tue, 19 Oct 2021 16:17:09 +0100 Subject: [PATCH 011/100] For space invite previews, use room summary API to get the right member count (#6982) --- res/css/structures/_SpaceRoomView.scss | 5 ++-- src/components/structures/SpaceRoomView.tsx | 30 +++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss index e116885047..e6394525c5 100644 --- a/res/css/structures/_SpaceRoomView.scss +++ b/res/css/structures/_SpaceRoomView.scss @@ -511,10 +511,11 @@ $SpaceRoomViewInnerWidth: 428px; mask-image: url("$(res)/img/element-icons/lock.svg"); } - .mx_AccessibleButton_kind_link { + .mx_SpaceRoomView_info_memberCount { color: inherit; position: relative; - padding-left: 16px; + padding: 0 0 0 16px; + font-size: $font-15px; &::before { content: "·"; // visual separator diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index dd12f76aac..db8f542b17 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -127,8 +127,17 @@ const useMyRoomMembership = (room: Room) => { return membership; }; -const SpaceInfo = ({ space }) => { +const SpaceInfo = ({ space }: { space: Room }) => { + const summary = useAsyncMemo(() => { + if (space.getMyMembership() !== "invite") return; + try { + return space.client.getRoomSummary(space.roomId); + } catch (e) { + return null; + } + }, [space]); const joinRule = useRoomState(space, state => state.getJoinRule()); + const membership = useMyRoomMembership(space); let visibilitySection; if (joinRule === "public") { @@ -141,12 +150,18 @@ const SpaceInfo = ({ space }) => { ; } - return
- { visibilitySection } - { joinRule === "public" && + let memberSection; + if (membership === "invite" && summary) { + // Don't trust local state and instead use the summary API + memberSection = + { _t("%(count)s members", { count: summary.num_joined_members }) } + ; + } else if (summary === null) { + memberSection = { (count) => count > 0 ? ( { defaultDispatcher.dispatch({ action: Action.SetRightPanelPhase, @@ -158,7 +173,12 @@ const SpaceInfo = ({ space }) => { { _t("%(count)s members", { count }) } ) : null } - } + ; + } + + return
+ { visibilitySection } + { memberSection }
; }; From 9c786717b8fd6a300e54b77af6b5877f9b54c664 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 19 Oct 2021 16:22:27 +0100 Subject: [PATCH 012/100] Validate email address in forgot password dialog (#6983) --- .../structures/auth/ForgotPassword.tsx | 37 ++++++++++++++++++- src/i18n/strings/en_EN.json | 1 + 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/components/structures/auth/ForgotPassword.tsx b/src/components/structures/auth/ForgotPassword.tsx index 2cb2a2a1a9..4c65fac983 100644 --- a/src/components/structures/auth/ForgotPassword.tsx +++ b/src/components/structures/auth/ForgotPassword.tsx @@ -29,8 +29,8 @@ import ServerPicker from "../../views/elements/ServerPicker"; import PassphraseField from '../../views/auth/PassphraseField'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm'; - -import { IValidationResult } from "../../views/elements/Validation"; +import withValidation, { IValidationResult } from "../../views/elements/Validation"; +import * as Email from "../../../email"; import InlineSpinner from '../../views/elements/InlineSpinner'; import { logger } from "matrix-js-sdk/src/logger"; @@ -68,6 +68,7 @@ interface IState { serverErrorIsFatal: boolean; serverDeadError: string; + emailFieldValid: boolean; passwordFieldValid: boolean; currentHttpRequest?: Promise; } @@ -90,6 +91,7 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", + emailFieldValid: false, passwordFieldValid: false, }; @@ -169,10 +171,13 @@ export default class ForgotPassword extends React.Component { // refresh the server errors, just in case the server came back online await this.handleHttpRequest(this.checkServerLiveliness(this.props.serverConfig)); + await this['email_field'].validate({ allowEmpty: false }); await this['password_field'].validate({ allowEmpty: false }); if (!this.state.email) { this.showErrorDialog(_t('The email address linked to your account must be entered.')); + } else if (!this.state.emailFieldValid) { + this.showErrorDialog(_t("The email address doesn't appear to be valid.")); } else if (!this.state.password || !this.state.password2) { this.showErrorDialog(_t('A new password must be entered.')); } else if (!this.state.passwordFieldValid) { @@ -222,6 +227,32 @@ export default class ForgotPassword extends React.Component { }); } + private validateEmailRules = withValidation({ + rules: [ + { + key: "required", + test({ value, allowEmpty }) { + return allowEmpty || !!value; + }, + invalid: () => _t("Enter email address"), + }, { + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }, + ], + }); + + private onEmailValidate = async (fieldState) => { + const result = await this.validateEmailRules(fieldState); + + this.setState({ + emailFieldValid: result.valid, + }); + + return result; + }; + private onPasswordValidate(result: IValidationResult) { this.setState({ passwordFieldValid: result.valid, @@ -277,7 +308,9 @@ export default class ForgotPassword extends React.Component { label={_t('Email')} value={this.state.email} onChange={this.onInputChanged.bind(this, "email")} + ref={field => this['email_field'] = field} autoFocus + onValidate={this.onEmailValidate} onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_email_blur")} /> diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9fab4afd42..594e6c9488 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3015,6 +3015,7 @@ "Skip verification for now": "Skip verification for now", "Failed to send email": "Failed to send email", "The email address linked to your account must be entered.": "The email address linked to your account must be entered.", + "The email address doesn't appear to be valid.": "The email address doesn't appear to be valid.", "A new password must be entered.": "A new password must be entered.", "Please choose a strong password": "Please choose a strong password", "New passwords must match each other.": "New passwords must match each other.", From a9c27e22b5a3a1050988be21bdc5ef45774628c7 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 19 Oct 2021 16:31:07 +0100 Subject: [PATCH 013/100] Handle and i18n M_THREEPID_IN_USE during registration (#6986) --- src/components/structures/auth/Registration.tsx | 13 ++++++++----- src/i18n/strings/en_EN.json | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 3c66a1ab86..ab1956dd6a 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -272,7 +272,7 @@ export default class Registration extends React.Component { private onUIAuthFinished = async (success: boolean, response: any) => { if (!success) { - let msg = response.message || response.toString(); + let errorText = response.message || response.toString(); // can we give a better error message? if (response.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') { const errorTop = messageForResourceLimitError( @@ -291,7 +291,7 @@ export default class Registration extends React.Component { '': _td("Please contact your service administrator to continue using this service."), }, ); - msg =
+ errorText =

{ errorTop }

{ errorDetail }

; @@ -301,15 +301,18 @@ export default class Registration extends React.Component { msisdnAvailable = msisdnAvailable || flow.stages.includes('m.login.msisdn'); } if (!msisdnAvailable) { - msg = _t('This server does not support authentication with a phone number.'); + errorText = _t('This server does not support authentication with a phone number.'); } } else if (response.errcode === "M_USER_IN_USE") { - msg = _t("That username already exists, please try another."); + errorText = _t("That username already exists, please try another."); + } else if (response.errcode === "M_THREEPID_IN_USE") { + errorText = _t("That e-mail address is already in use."); } + this.setState({ busy: false, doingUIAuth: false, - errorText: msg, + errorText, }); return; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 594e6c9488..cfe8726325 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -3056,6 +3056,7 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "That username already exists, please try another.": "That username already exists, please try another.", + "That e-mail address is already in use.": "That e-mail address is already in use.", "Continue with %(ssoButtons)s": "Continue with %(ssoButtons)s", "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s Or %(usernamePassword)s", "Already have an account? Sign in here": "Already have an account? Sign in here", From 427afc83aa85a8bf8c985c867c7d6ad12df3d071 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 19 Oct 2021 17:10:34 +0100 Subject: [PATCH 014/100] Fix couple of Spaces regressions (#6989) --- res/css/structures/_SpacePanel.scss | 7 ------- src/components/structures/SpaceRoomView.tsx | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index d060696147..316bc460b5 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -146,13 +146,6 @@ $activeBorderColor: $secondary-content; padding: 4px; } - &:not(.mx_SpaceButton_narrow) { - .mx_SpaceButton_selectionWrapper { - width: 100%; - overflow: hidden; - } - } - .mx_SpaceButton_name { flex: 1; margin-left: 8px; diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index db8f542b17..4a43f0380d 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -128,7 +128,7 @@ const useMyRoomMembership = (room: Room) => { }; const SpaceInfo = ({ space }: { space: Room }) => { - const summary = useAsyncMemo(() => { + const summary = useAsyncMemo(async () => { if (space.getMyMembership() !== "invite") return; try { return space.client.getRoomSummary(space.roomId); From 0d29626163be4f78e20ae84713c84fee8143db68 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Tue, 19 Oct 2021 11:22:04 -0600 Subject: [PATCH 015/100] Update allchange (#6990) --- package.json | 2 +- yarn.lock | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index c3cd054db8..d305b6f889 100644 --- a/package.json +++ b/package.json @@ -154,7 +154,7 @@ "@typescript-eslint/eslint-plugin": "^4.17.0", "@typescript-eslint/parser": "^4.17.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1", - "allchange": "^1.0.3", + "allchange": "^1.0.4", "babel-jest": "^26.6.3", "chokidar": "^3.5.1", "concurrently": "^5.3.0", diff --git a/yarn.lock b/yarn.lock index 5c739e58c4..7603c3448e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,6 +1308,7 @@ "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": version "3.2.3" + uid cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4 resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3": @@ -2052,10 +2053,10 @@ ajv@^8.0.1: require-from-string "^2.0.2" uri-js "^4.2.2" -allchange@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.3.tgz#f8814ddfbcfe39a01bf4570778ee7e6d9ff0ebb3" - integrity sha512-UZkfz5SkNEMFQFLr8vZcXHaph2EbJxmkVNF5Nt6D9RIa5pmAar7oAMfNdda714jg7IQijvaFty5PYazXLgd5WA== +allchange@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/allchange/-/allchange-1.0.4.tgz#c540d9344e7e5bf4187be823ad5d858bed365d82" + integrity sha512-9V2PPRj2UtkqFIx1DFPS5ubadTpu59fMrIteA3JUVpv6UrVCFeFRELWrOwUBHlQmsG4ud82ru34k8yJ3VKeqHg== dependencies: "@actions/core" "^1.4.0" "@actions/github" "^5.0.0" From 01c4d3eede9853da080afe9601dfd82fca715f52 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 19 Oct 2021 18:43:29 +0100 Subject: [PATCH 016/100] Fix conflicting CSS on syntax highlighted blocks (#6991) Fixes https://github.com/vector-im/element-web/issues/19445 --- res/css/views/rooms/_EventTile.scss | 4 ---- src/components/views/messages/TextualBody.tsx | 7 +++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 540b7d89af..ea53cc4f13 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -589,10 +589,6 @@ $hover-select-border: 4px; padding: 0 10px; } -.mx_EventTile_content .markdown-body .hljs { - display: inline !important; -} - /* // actually, removing the Italic TTF provides // better results seemingly diff --git a/src/components/views/messages/TextualBody.tsx b/src/components/views/messages/TextualBody.tsx index 941c269991..b8e068ed75 100644 --- a/src/components/views/messages/TextualBody.tsx +++ b/src/components/views/messages/TextualBody.tsx @@ -232,7 +232,6 @@ export default class TextualBody extends React.Component { ); return; } - console.log('highlighting'); let advertisedLang; for (const cl of code.className.split(/\s+/)) { @@ -258,7 +257,11 @@ export default class TextualBody extends React.Component { // User has language detection enabled and the code is within a pre // we only auto-highlight if the code block is in a pre), so highlight // the block with auto-highlighting enabled. - highlight.highlightElement(code); + // We pass highlightjs the text to highlight rather than letting it + // work on the DOM with highlightElement because that also adds CSS + // classes to the pre/code element that we don't want (the CSS + // conflicts with our own). + code.innerHTML = highlight.highlightAuto(code.textContent).value; } } From 23295718e3aabfe2c8d14c9b7918e140f98bf5b3 Mon Sep 17 00:00:00 2001 From: CicadaCinema <52425971+CicadaCinema@users.noreply.github.com> Date: Tue, 19 Oct 2021 22:28:11 +0100 Subject: [PATCH 017/100] Position toggle switch more clearly (#6914) * Position toggle switch more clearly * attempt to revert changes, align switch to title instead * remove unused import, add missing property Co-authored-by: ColonisationCaptain <52425971+ColonisationCaptain@users.noreply.github.com> --- res/css/views/settings/_SetIntegrationManager.scss | 3 +-- .../views/settings/SetIntegrationManager.tsx | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/res/css/views/settings/_SetIntegrationManager.scss b/res/css/views/settings/_SetIntegrationManager.scss index 3e59ac73ac..521b1ee8ab 100644 --- a/res/css/views/settings/_SetIntegrationManager.scss +++ b/res/css/views/settings/_SetIntegrationManager.scss @@ -26,12 +26,11 @@ limitations under the License. .mx_SetIntegrationManager > .mx_SettingsTab_heading > .mx_SettingsTab_subheading { display: inline-block; padding-left: 5px; + margin-top: 0px; } .mx_SetIntegrationManager .mx_ToggleSwitch { display: inline-block; float: right; top: 9px; - - @mixin mx_Settings_fullWidthField; } diff --git a/src/components/views/settings/SetIntegrationManager.tsx b/src/components/views/settings/SetIntegrationManager.tsx index 7ecc6d1134..dc33a9e20e 100644 --- a/src/components/views/settings/SetIntegrationManager.tsx +++ b/src/components/views/settings/SetIntegrationManager.tsx @@ -18,10 +18,10 @@ import React from 'react'; import { _t } from "../../../languageHandler"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import { IntegrationManagerInstance } from "../../../integrations/IntegrationManagerInstance"; -import * as sdk from '../../../index'; import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; import { replaceableComponent } from "../../../utils/replaceableComponent"; +import ToggleSwitch from "../elements/ToggleSwitch"; import { logger } from "matrix-js-sdk/src/logger"; @@ -59,8 +59,6 @@ export default class SetIntegrationManager extends React.Component { _t("Manage integrations") } { managerName } - +
{ bodyText } From 1fa4c40db9e0558d77fe6ff5139ad50473d62259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Gon=C3=A7alves?= <8217676+RafaelGoncalves8@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:36:08 -0300 Subject: [PATCH 018/100] Remove redundant text in verification dialogs (#6993) * Remove redundant text Signed-off-by: Rafael Goncalves * Run yarn i18n Signed-off-by: Rafael Goncalves --- src/components/views/right_panel/VerificationPanel.tsx | 2 -- src/i18n/strings/en_EN.json | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index eb59fbb15d..881dac33d4 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -252,7 +252,6 @@ export default class VerificationPanel extends React.PureComponent -

{ _t("Verified") }

{ description }

{ text ?

{ text }

: null } @@ -325,7 +324,6 @@ export default class VerificationPanel extends React.PureComponent : ; return
-

{ _t("Compare emoji") }

{ emojis }
; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index cfe8726325..51fd51316a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1931,7 +1931,6 @@ "You've successfully verified your device!": "You've successfully verified your device!", "You've successfully verified %(deviceName)s (%(deviceId)s)!": "You've successfully verified %(deviceName)s (%(deviceId)s)!", "You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!", - "Verified": "Verified", "Got it": "Got it", "Start verification again from the notification.": "Start verification again from the notification.", "Start verification again from their profile.": "Start verification again from their profile.", @@ -1940,7 +1939,6 @@ "%(displayName)s cancelled verification.": "%(displayName)s cancelled verification.", "You cancelled verification.": "You cancelled verification.", "Verification cancelled": "Verification cancelled", - "Compare emoji": "Compare emoji", "Call declined": "Call declined", "Call back": "Call back", "No answer": "No answer", From 8b6d5d59c742b3e7b3a0140d80dafeeaa0e87a4a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 20 Oct 2021 12:12:20 +0100 Subject: [PATCH 019/100] Fix space panel name overflowing (#6995) --- res/css/structures/_SpacePanel.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss index 316bc460b5..f590de18c1 100644 --- a/res/css/structures/_SpacePanel.scss +++ b/res/css/structures/_SpacePanel.scss @@ -144,6 +144,7 @@ $activeBorderColor: $secondary-content; align-items: center; border-radius: 12px; padding: 4px; + width: 100%; } .mx_SpaceButton_name { From 96bd052ecf025c8dc3526ec9fba1d7fc5d175d05 Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 20 Oct 2021 13:41:27 +0100 Subject: [PATCH 020/100] Improve visibility of font size chooser (#6988) * Improve visibility of font size chooser * Move slider dot sizes into variables * Use a standard font size instead of percentage in Slider * Use shorthand for padding in FontScalingPanel * Change Slider text pos to px to be consistent when font changes * Cleaner dot size for Slider selection --- res/css/_common.scss | 3 +++ res/css/views/elements/_Slider.scss | 17 +++++++++++++---- res/css/views/settings/_FontScalingPanel.scss | 2 +- src/components/views/elements/Slider.tsx | 4 +++- .../FontScalingPanel-test.tsx.snap | 10 ++++++++-- 5 files changed, 28 insertions(+), 8 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index d7f8355d81..2242658ae8 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -27,6 +27,9 @@ $EventTile_e2e_state_indicator_width: 4px; $MessageTimestamp_width: 46px; /* 8 + 30 (avatar) + 8 */ $MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $EventTile_e2e_state_indicator_width); +$slider-dot-size: 1em; +$slider-selection-dot-size: 2.4em; + :root { font-size: 10px; diff --git a/res/css/views/elements/_Slider.scss b/res/css/views/elements/_Slider.scss index 58ba2813b4..730da021bd 100644 --- a/res/css/views/elements/_Slider.scss +++ b/res/css/views/elements/_Slider.scss @@ -54,22 +54,31 @@ limitations under the License. .mx_Slider_selectionDot { position: absolute; - width: 1.1em; - height: 1.1em; + width: $slider-selection-dot-size; + height: $slider-selection-dot-size; background-color: $slider-selection-color; border-radius: 50%; box-shadow: 0 0 6px lightgrey; z-index: 10; } +.mx_Slider_selectionText { + color: $muted-fg-color; + font-size: $font-14px; + position: relative; + text-align: center; + top: 30px; + width: 100%; +} + .mx_Slider_selection > hr { margin: 0; border: 0.2em solid $slider-selection-color; } .mx_Slider_dot { - height: 1em; - width: 1em; + height: $slider-dot-size; + width: $slider-dot-size; border-radius: 50%; background-color: $slider-background-color; z-index: 0; diff --git a/res/css/views/settings/_FontScalingPanel.scss b/res/css/views/settings/_FontScalingPanel.scss index bac1f92a4f..ff2e905320 100644 --- a/res/css/views/settings/_FontScalingPanel.scss +++ b/res/css/views/settings/_FontScalingPanel.scss @@ -34,7 +34,7 @@ limitations under the License. display: flex; flex-direction: row; align-items: center; - padding: 15px; + padding: 15px 15px 35px; background: rgba($appearance-tab-border-color, 0.2); border-radius: 10px; font-size: 10px; diff --git a/src/components/views/elements/Slider.tsx b/src/components/views/elements/Slider.tsx index df5776648e..a201659e3c 100644 --- a/src/components/views/elements/Slider.tsx +++ b/src/components/views/elements/Slider.tsx @@ -86,7 +86,9 @@ export default class Slider extends React.Component { if (!this.props.disabled) { const offset = this.offset(this.props.values, this.props.value); selection =
-
+
+
{ this.props.value }
+

; } diff --git a/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap index 2174b4f289..86163ab762 100644 --- a/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap +++ b/test/components/views/settings/__snapshots__/FontScalingPanel-test.tsx.snap @@ -83,10 +83,16 @@ exports[`FontScalingPanel renders the font scaling UI 1`] = ` className="mx_Slider_selectionDot" style={ Object { - "left": "calc(-0.55em + 50%)", + "left": "calc(-1.195em + 50%)", } } - /> + > +
+ 15 +
+

Date: Wed, 20 Oct 2021 06:55:22 -0600 Subject: [PATCH 021/100] Add a developer mode flag and use it for accessing space timelines (#6994) Fixes https://github.com/vector-im/element-web/issues/19416 --- src/components/structures/LoggedInView.tsx | 2 ++ src/components/structures/MatrixChat.tsx | 5 ++++ src/components/structures/RoomView.tsx | 4 ++- .../views/context_menus/SpaceContextMenu.tsx | 25 +++++++++++++++++++ .../tabs/user/LabsUserSettingsTab.tsx | 1 + src/i18n/strings/en_EN.json | 2 ++ src/settings/Settings.tsx | 5 ++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx index 84e0b446f5..566e14e633 100644 --- a/src/components/structures/LoggedInView.tsx +++ b/src/components/structures/LoggedInView.tsx @@ -108,6 +108,7 @@ interface IProps { currentGroupIsNew?: boolean; justRegistered?: boolean; roomJustCreatedOpts?: IOpts; + forceTimeline?: boolean; // see props on MatrixChat } interface IUsageLimit { @@ -611,6 +612,7 @@ class LoggedInView extends React.Component { key={this.props.currentRoomId || 'roomview'} resizeNotifier={this.props.resizeNotifier} justCreatedOpts={this.props.roomJustCreatedOpts} + forceTimeline={this.props.forceTimeline} />; break; diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index f0a7e634b0..0c68f2013c 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -176,6 +176,9 @@ interface IRoomInfo { threepid_invite?: IThreepidInvite; justCreatedOpts?: IOpts; + + // Whether or not to override default behaviour to end up at a timeline + forceTimeline?: boolean; } /* eslint-enable camelcase */ @@ -238,6 +241,7 @@ interface IState { pendingInitialSync?: boolean; justRegistered?: boolean; roomJustCreatedOpts?: IOpts; + forceTimeline?: boolean; // see props } @replaceableComponent("structures.MatrixChat") @@ -968,6 +972,7 @@ export default class MatrixChat extends React.PureComponent { page_type: PageType.RoomView, threepidInvite: roomInfo.threepid_invite, roomOobData: roomInfo.oob_data, + forceTimeline: roomInfo.forceTimeline, ready: true, roomJustCreatedOpts: roomInfo.justCreatedOpts, }, () => { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index c2e071b836..7af67a8267 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -110,6 +110,8 @@ interface IRoomProps extends MatrixClientProps { resizeNotifier: ResizeNotifier; justCreatedOpts?: IOpts; + forceTimeline?: boolean; // should we force access to the timeline, overriding (for eg) spaces + // Called with the credentials of a registered user (if they were a ROU that transitioned to PWLU) onRegistered?(credentials: IMatrixClientCreds): void; } @@ -1911,7 +1913,7 @@ export class RoomView extends React.Component { ); } - if (this.state.room?.isSpaceRoom()) { + if (this.state.room?.isSpaceRoom() && !this.props.forceTimeline) { return { ; } + let devtoolsSection; + if (SettingsStore.getValue("developerMode")) { + const onViewTimelineClick = (ev: ButtonEvent) => { + ev.preventDefault(); + ev.stopPropagation(); + + defaultDispatcher.dispatch({ + action: 'view_room', + room_id: space.roomId, + forceTimeline: true, + }); + onFinished(); + }; + + devtoolsSection = + + ; + } + const canAddRooms = space.currentState.maySendStateEvent(EventType.SpaceChild, userId); let newRoomSection; @@ -209,6 +233,7 @@ const SpaceContextMenu = ({ space, onFinished, ...props }: IProps) => { { newRoomSection } { leaveSection } + { devtoolsSection } ; }; diff --git a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx index 60461a114c..d05a11483c 100644 --- a/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/LabsUserSettingsTab.tsx @@ -87,6 +87,7 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> { } labsSection =
+ { flags } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 51fd51316a..b8252dde83 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -886,6 +886,7 @@ "All rooms you're in will appear in Home.": "All rooms you're in will appear in Home.", "Display Communities instead of Spaces": "Display Communities instead of Spaces", "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.", + "Developer mode": "Developer mode", "Collecting app version information": "Collecting app version information", "Collecting logs": "Collecting logs", "Uploading logs": "Uploading logs", @@ -2701,6 +2702,7 @@ "Collapse reply thread": "Collapse reply thread", "Report": "Report", "View in room": "View in room", + "See room timeline (devtools)": "See room timeline (devtools)", "Add space": "Add space", "Manage & explore rooms": "Manage & explore rooms", "Clear status": "Clear status", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index d36ea2ac94..ab3b164517 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -757,6 +757,11 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new ReloadOnChangeController(), }, + "developerMode": { + displayName: _td("Developer mode"), + supportedLevels: LEVELS_ACCOUNT_SETTINGS, + default: false, + }, [UIFeature.RoomHistorySettings]: { supportedLevels: LEVELS_UI_FEATURE, default: true, From 4b903b9fbdd32f53402f1e9c18337bab1785d4ec Mon Sep 17 00:00:00 2001 From: Andy Balaam Date: Wed, 20 Oct 2021 15:58:27 +0100 Subject: [PATCH 022/100] Break ThemeChoicePanel into a separate component (#6998) * Break ThemeChoicePanel into a separate component * Tests for ThemeChoicePanel * i18n fixes * Fix copyright for ThemeChoicePanel --- res/css/_components.scss | 1 + res/css/views/settings/_ThemeChoicePanel.scss | 88 +++++++ .../tabs/user/_AppearanceUserSettingsTab.scss | 72 ------ .../views/settings/ThemeChoicePanel.tsx | 240 ++++++++++++++++++ .../tabs/user/AppearanceUserSettingsTab.tsx | 154 +---------- src/i18n/strings/en_EN.json | 12 +- .../views/settings/ThemeChoicePanel-test.tsx | 60 +++++ .../ThemeChoicePanel-test.tsx.snap | 111 ++++++++ 8 files changed, 508 insertions(+), 230 deletions(-) create mode 100644 res/css/views/settings/_ThemeChoicePanel.scss create mode 100644 src/components/views/settings/ThemeChoicePanel.tsx create mode 100644 test/components/views/settings/ThemeChoicePanel-test.tsx create mode 100644 test/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap diff --git a/res/css/_components.scss b/res/css/_components.scss index 8ea46c4243..26e36b8cdd 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -259,6 +259,7 @@ @import "./views/settings/_SetIdServer.scss"; @import "./views/settings/_SetIntegrationManager.scss"; @import "./views/settings/_SpellCheckLanguages.scss"; +@import "./views/settings/_ThemeChoicePanel.scss"; @import "./views/settings/_UpdateCheckButton.scss"; @import "./views/settings/tabs/_SettingsTab.scss"; @import "./views/settings/tabs/room/_GeneralRoomSettingsTab.scss"; diff --git a/res/css/views/settings/_ThemeChoicePanel.scss b/res/css/views/settings/_ThemeChoicePanel.scss new file mode 100644 index 0000000000..39b73e7837 --- /dev/null +++ b/res/css/views/settings/_ThemeChoicePanel.scss @@ -0,0 +1,88 @@ +/* +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_ThemeChoicePanel { + $radio-bg-color: $input-darker-bg-color; + color: $primary-content; + + > .mx_ThemeSelectors { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + margin-top: 4px; + margin-bottom: 30px; + + > .mx_RadioButton { + padding: $font-16px; + box-sizing: border-box; + border-radius: 10px; + width: 180px; + + background: $radio-bg-color; + opacity: 0.4; + + flex-shrink: 1; + flex-grow: 0; + + margin-right: 15px; + margin-top: 10px; + + font-weight: 600; + color: $muted-fg-color; + + > span { + justify-content: center; + } + } + + > .mx_RadioButton_enabled { + opacity: 1; + + // These colors need to be hardcoded because they don't change with the theme + &.mx_ThemeSelector_light { + background-color: #f3f8fd; + color: #2e2f32; + } + + &.mx_ThemeSelector_dark { + // 5% lightened version of 181b21 + background-color: #25282e; + color: #f3f8fd; + + > input > div { + border-color: $input-darker-bg-color; + > div { + border-color: $input-darker-bg-color; + } + } + } + + &.mx_ThemeSelector_black { + background-color: #000000; + color: #f3f8fd; + + > input > div { + border-color: $input-darker-bg-color; + > div { + border-color: $input-darker-bg-color; + } + } + } + } + } +} + diff --git a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss index 21082786e9..c237856e60 100644 --- a/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss +++ b/res/css/views/settings/tabs/user/_AppearanceUserSettingsTab.scss @@ -24,78 +24,6 @@ limitations under the License. } } -.mx_AppearanceUserSettingsTab_themeSection { - $radio-bg-color: $input-darker-bg-color; - color: $primary-content; - - > .mx_ThemeSelectors { - display: flex; - flex-direction: row; - flex-wrap: wrap; - - margin-top: 4px; - margin-bottom: 30px; - - > .mx_RadioButton { - padding: $font-16px; - box-sizing: border-box; - border-radius: 10px; - width: 180px; - - background: $radio-bg-color; - opacity: 0.4; - - flex-shrink: 1; - flex-grow: 0; - - margin-right: 15px; - margin-top: 10px; - - font-weight: 600; - color: $muted-fg-color; - - > span { - justify-content: center; - } - } - - > .mx_RadioButton_enabled { - opacity: 1; - - // These colors need to be hardcoded because they don't change with the theme - &.mx_ThemeSelector_light { - background-color: #f3f8fd; - color: #2e2f32; - } - - &.mx_ThemeSelector_dark { - // 5% lightened version of 181b21 - background-color: #25282e; - color: #f3f8fd; - - > input > div { - border-color: $input-darker-bg-color; - > div { - border-color: $input-darker-bg-color; - } - } - } - - &.mx_ThemeSelector_black { - background-color: #000000; - color: #f3f8fd; - - > input > div { - border-color: $input-darker-bg-color; - > div { - border-color: $input-darker-bg-color; - } - } - } - } - } -} - .mx_AppearanceUserSettingsTab_Advanced { color: $primary-content; diff --git a/src/components/views/settings/ThemeChoicePanel.tsx b/src/components/views/settings/ThemeChoicePanel.tsx new file mode 100644 index 0000000000..caa07bd0ad --- /dev/null +++ b/src/components/views/settings/ThemeChoicePanel.tsx @@ -0,0 +1,240 @@ +/* +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 { _t } from "../../../languageHandler"; +import SettingsStore from "../../../settings/SettingsStore"; +import { enumerateThemes } from "../../../theme"; +import ThemeWatcher from "../../../settings/watchers/ThemeWatcher"; +import AccessibleButton from "../elements/AccessibleButton"; +import dis from "../../../dispatcher/dispatcher"; +import { RecheckThemePayload } from '../../../dispatcher/payloads/RecheckThemePayload'; +import { Action } from '../../../dispatcher/actions'; +import StyledCheckbox from '../elements/StyledCheckbox'; +import Field from '../elements/Field'; +import StyledRadioGroup from "../elements/StyledRadioGroup"; +import { SettingLevel } from "../../../settings/SettingLevel"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; +import { compare } from "../../../utils/strings"; + +import { logger } from "matrix-js-sdk/src/logger"; + +interface IProps { +} + +interface IThemeState { + theme: string; + useSystemTheme: boolean; +} + +export interface CustomThemeMessage { + isError: boolean; + text: string; +} + +interface IState extends IThemeState { + customThemeUrl: string; + customThemeMessage: CustomThemeMessage; +} + +@replaceableComponent("views.settings.tabs.user.ThemeChoicePanel") +export default class ThemeChoicePanel extends React.Component { + private themeTimer: number; + + constructor(props: IProps) { + super(props); + + this.state = { + ...this.calculateThemeState(), + customThemeUrl: "", + customThemeMessage: { isError: false, text: "" }, + }; + } + + private calculateThemeState(): IThemeState { + // We have to mirror the logic from ThemeWatcher.getEffectiveTheme so we + // show the right values for things. + + const themeChoice: string = SettingsStore.getValue("theme"); + const systemThemeExplicit: boolean = SettingsStore.getValueAt( + SettingLevel.DEVICE, "use_system_theme", null, false, true); + const themeExplicit: string = SettingsStore.getValueAt( + SettingLevel.DEVICE, "theme", null, false, true); + + // If the user has enabled system theme matching, use that. + if (systemThemeExplicit) { + return { + theme: themeChoice, + useSystemTheme: true, + }; + } + + // If the user has set a theme explicitly, use that (no system theme matching) + if (themeExplicit) { + return { + theme: themeChoice, + useSystemTheme: false, + }; + } + + // Otherwise assume the defaults for the settings + return { + theme: themeChoice, + useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"), + }; + } + + private onThemeChange = (newTheme: string): void => { + if (this.state.theme === newTheme) return; + + // doing getValue in the .catch will still return the value we failed to set, + // so remember what the value was before we tried to set it so we can revert + const oldTheme: string = SettingsStore.getValue('theme'); + SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme).catch(() => { + dis.dispatch({ action: Action.RecheckTheme }); + this.setState({ theme: oldTheme }); + }); + this.setState({ theme: newTheme }); + // The settings watcher doesn't fire until the echo comes back from the + // server, so to make the theme change immediately we need to manually + // do the dispatch now + // XXX: The local echoed value appears to be unreliable, in particular + // when settings custom themes(!) so adding forceTheme to override + // the value from settings. + dis.dispatch({ action: Action.RecheckTheme, forceTheme: newTheme }); + }; + + private onUseSystemThemeChanged = (checked: boolean): void => { + this.setState({ useSystemTheme: checked }); + SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked); + dis.dispatch({ action: Action.RecheckTheme }); + }; + + private onAddCustomTheme = async (): Promise => { + let currentThemes: string[] = SettingsStore.getValue("custom_themes"); + if (!currentThemes) currentThemes = []; + currentThemes = currentThemes.map(c => c); // cheap clone + + if (this.themeTimer) { + clearTimeout(this.themeTimer); + } + + try { + const r = await fetch(this.state.customThemeUrl); + // XXX: need some schema for this + const themeInfo = await r.json(); + if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { + this.setState({ customThemeMessage: { text: _t("Invalid theme schema."), isError: true } }); + return; + } + currentThemes.push(themeInfo); + } catch (e) { + logger.error(e); + this.setState({ customThemeMessage: { text: _t("Error downloading theme information."), isError: true } }); + return; // Don't continue on error + } + + await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); + this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } }); + + this.themeTimer = setTimeout(() => { + this.setState({ customThemeMessage: { text: "", isError: false } }); + }, 3000); + }; + + private onCustomThemeChange = (e: React.ChangeEvent): void => { + this.setState({ customThemeUrl: e.target.value }); + }; + + public render() { + const themeWatcher = new ThemeWatcher(); + let systemThemeSection: JSX.Element; + if (themeWatcher.isSystemThemeSupported()) { + systemThemeSection =
+ this.onUseSystemThemeChanged(e.target.checked)} + > + { SettingsStore.getDisplayName("use_system_theme") } + +
; + } + + let customThemeForm: JSX.Element; + if (SettingsStore.getValue("feature_custom_themes")) { + let messageElement = null; + if (this.state.customThemeMessage.text) { + if (this.state.customThemeMessage.isError) { + messageElement =
{ this.state.customThemeMessage.text }
; + } else { + messageElement =
{ this.state.customThemeMessage.text }
; + } + } + customThemeForm = ( +
+
+ + + { _t("Add theme") } + + { messageElement } + +
+ ); + } + + // XXX: replace any type here + const themes = Object.entries(enumerateThemes()) + .map(p => ({ id: p[0], name: p[1] })); // convert pairs to objects for code readability + const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); + const customThemes = themes.filter(p => !builtInThemes.includes(p)) + .sort((a, b) => compare(a.name, b.name)); + const orderedThemes = [...builtInThemes, ...customThemes]; + return ( +
+ { _t("Theme") } + { systemThemeSection } +
+ ({ + value: t.id, + label: t.name, + disabled: this.state.useSystemTheme, + className: "mx_ThemeSelector_" + t.id, + }))} + onChange={this.onThemeChange} + value={this.state.useSystemTheme ? undefined : this.state.theme} + outlined + /> +
+ { customThemeForm } +
+ ); + } +} diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index 3abb90d2a9..404fa9504f 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -20,25 +20,17 @@ import { _t } from "../../../../../languageHandler"; import SdkConfig from "../../../../../SdkConfig"; import { MatrixClientPeg } from '../../../../../MatrixClientPeg'; import SettingsStore from "../../../../../settings/SettingsStore"; -import { enumerateThemes } from "../../../../../theme"; -import ThemeWatcher from "../../../../../settings/watchers/ThemeWatcher"; -import AccessibleButton from "../../../elements/AccessibleButton"; -import dis from "../../../../../dispatcher/dispatcher"; -import { RecheckThemePayload } from '../../../../../dispatcher/payloads/RecheckThemePayload'; -import { Action } from '../../../../../dispatcher/actions'; import StyledCheckbox from '../../../elements/StyledCheckbox'; import SettingsFlag from '../../../elements/SettingsFlag'; import Field from '../../../elements/Field'; -import StyledRadioGroup from "../../../elements/StyledRadioGroup"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; import { Layout } from "../../../../../settings/Layout"; import { replaceableComponent } from "../../../../../utils/replaceableComponent"; -import { compare } from "../../../../../utils/strings"; import LayoutSwitcher from "../../LayoutSwitcher"; -import { logger } from "matrix-js-sdk/src/logger"; import FontScalingPanel from '../../FontScalingPanel'; +import ThemeChoicePanel from '../../ThemeChoicePanel'; interface IProps { } @@ -70,7 +62,6 @@ interface IState extends IThemeState { export default class AppearanceUserSettingsTab extends React.Component { private readonly MESSAGE_PREVIEW_TEXT = _t("Hey you. You're the best!"); - private themeTimer: number; private unmounted = false; constructor(props: IProps) { @@ -141,68 +132,6 @@ export default class AppearanceUserSettingsTab extends React.Component { - if (this.state.theme === newTheme) return; - - // doing getValue in the .catch will still return the value we failed to set, - // so remember what the value was before we tried to set it so we can revert - const oldTheme: string = SettingsStore.getValue('theme'); - SettingsStore.setValue("theme", null, SettingLevel.DEVICE, newTheme).catch(() => { - dis.dispatch({ action: Action.RecheckTheme }); - this.setState({ theme: oldTheme }); - }); - this.setState({ theme: newTheme }); - // The settings watcher doesn't fire until the echo comes back from the - // server, so to make the theme change immediately we need to manually - // do the dispatch now - // XXX: The local echoed value appears to be unreliable, in particular - // when settings custom themes(!) so adding forceTheme to override - // the value from settings. - dis.dispatch({ action: Action.RecheckTheme, forceTheme: newTheme }); - }; - - private onUseSystemThemeChanged = (checked: boolean): void => { - this.setState({ useSystemTheme: checked }); - SettingsStore.setValue("use_system_theme", null, SettingLevel.DEVICE, checked); - dis.dispatch({ action: Action.RecheckTheme }); - }; - - private onAddCustomTheme = async (): Promise => { - let currentThemes: string[] = SettingsStore.getValue("custom_themes"); - if (!currentThemes) currentThemes = []; - currentThemes = currentThemes.map(c => c); // cheap clone - - if (this.themeTimer) { - clearTimeout(this.themeTimer); - } - - try { - const r = await fetch(this.state.customThemeUrl); - // XXX: need some schema for this - const themeInfo = await r.json(); - if (!themeInfo || typeof(themeInfo['name']) !== 'string' || typeof(themeInfo['colors']) !== 'object') { - this.setState({ customThemeMessage: { text: _t("Invalid theme schema."), isError: true } }); - return; - } - currentThemes.push(themeInfo); - } catch (e) { - logger.error(e); - this.setState({ customThemeMessage: { text: _t("Error downloading theme information."), isError: true } }); - return; // Don't continue on error - } - - await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes); - this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } }); - - this.themeTimer = setTimeout(() => { - this.setState({ customThemeMessage: { text: "", isError: false } }); - }, 3000); - }; - - private onCustomThemeChange = (e: React.ChangeEvent): void => { - this.setState({ customThemeUrl: e.target.value }); - }; - private onLayoutChanged = (layout: Layout): void => { this.setState({ layout: layout }); }; @@ -217,85 +146,6 @@ export default class AppearanceUserSettingsTab extends React.Component - this.onUseSystemThemeChanged(e.target.checked)} - > - { SettingsStore.getDisplayName("use_system_theme") } - -
; - } - - let customThemeForm: JSX.Element; - if (SettingsStore.getValue("feature_custom_themes")) { - let messageElement = null; - if (this.state.customThemeMessage.text) { - if (this.state.customThemeMessage.isError) { - messageElement =
{ this.state.customThemeMessage.text }
; - } else { - messageElement =
{ this.state.customThemeMessage.text }
; - } - } - customThemeForm = ( -
-
- - - { _t("Add theme") } - - { messageElement } - -
- ); - } - - // XXX: replace any type here - const themes = Object.entries(enumerateThemes()) - .map(p => ({ id: p[0], name: p[1] })); // convert pairs to objects for code readability - const builtInThemes = themes.filter(p => !p.id.startsWith("custom-")); - const customThemes = themes.filter(p => !builtInThemes.includes(p)) - .sort((a, b) => compare(a.name, b.name)); - const orderedThemes = [...builtInThemes, ...customThemes]; - return ( -
- { _t("Theme") } - { systemThemeSection } -
- ({ - value: t.id, - label: t.name, - disabled: this.state.useSystemTheme, - className: "mx_ThemeSelector_" + t.id, - }))} - onChange={this.onThemeChange} - value={this.state.useSystemTheme ? undefined : this.state.theme} - outlined - /> -
- { customThemeForm } -
- ); - } - private renderAdvancedSection() { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -382,7 +232,7 @@ export default class AppearanceUserSettingsTab extends React.Component { _t("Appearance Settings only affect this %(brand)s session.", { brand }) }
- { this.renderThemeSection() } + { layoutSection } { this.renderAdvancedSection() } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b8252dde83..68f4fca183 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1288,18 +1288,18 @@ "Manage integrations": "Manage integrations", "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.", "Add": "Add", - "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", - "Checking for an update...": "Checking for an update...", - "No update available.": "No update available.", - "Downloading update...": "Downloading update...", - "New version available. Update now.": "New version available. Update now.", - "Check for update": "Check for update", "Invalid theme schema.": "Invalid theme schema.", "Error downloading theme information.": "Error downloading theme information.", "Theme added!": "Theme added!", "Custom theme URL": "Custom theme URL", "Add theme": "Add theme", "Theme": "Theme", + "Error encountered (%(errorDetail)s).": "Error encountered (%(errorDetail)s).", + "Checking for an update...": "Checking for an update...", + "No update available.": "No update available.", + "Downloading update...": "Downloading update...", + "New version available. Update now.": "New version available. Update now.", + "Check for update": "Check for update", "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.", "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", "Customise your appearance": "Customise your appearance", diff --git a/test/components/views/settings/ThemeChoicePanel-test.tsx b/test/components/views/settings/ThemeChoicePanel-test.tsx new file mode 100644 index 0000000000..db342f8df8 --- /dev/null +++ b/test/components/views/settings/ThemeChoicePanel-test.tsx @@ -0,0 +1,60 @@ +/* +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 _ThemeChoicePanel from '../../../../src/components/views/settings/ThemeChoicePanel'; + +const ThemeChoicePanel = TestUtils.wrapInMatrixClientContext(_ThemeChoicePanel); + +// Avoid errors about global.matchMedia. See: +// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); + +// Fake random strings to give a predictable snapshot +jest.mock( + 'matrix-js-sdk/src/randomstring', + () => { + return { + randomString: () => "abdefghi", + }; + }, +); + +describe('ThemeChoicePanel', () => { + it('renders the theme choice UI', () => { + TestUtils.stubClient(); + const wrapper = mount( + , + ); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/test/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap b/test/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap new file mode 100644 index 0000000000..a760e9d466 --- /dev/null +++ b/test/components/views/settings/__snapshots__/ThemeChoicePanel-test.tsx.snap @@ -0,0 +1,111 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ThemeChoicePanel renders the theme choice UI 1`] = ` + + +
+ + Theme + +
+ + +