From 689d524b0251e0f4c3a424f28e4716077c61592e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 12:49:37 -0500 Subject: [PATCH 01/19] Upgrade react-textarea-autosize to 8.3.3 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8f64a1475..6d5053cf0 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "react-router-scroll-4": "^1.0.0-beta.1", "react-sparklines": "^1.7.0", "react-swipeable-views": "^0.13.0", - "react-textarea-autosize": "^8.0.0", + "react-textarea-autosize": "^8.3.3", "react-toggle": "^4.0.1", "redux": "^4.0.5", "redux-immutable": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 60526857c..64ed758b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10277,10 +10277,10 @@ react-test-renderer@^16.13.1: react-is "^16.8.6" scheduler "^0.19.1" -react-textarea-autosize@^8.0.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz#fae38653f5ec172a855fd5fffb39e466d56aebdb" - integrity sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw== +react-textarea-autosize@^8.3.3: + version "8.3.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" + integrity sha512-2XlHXK2TDxS6vbQaoPbMOfQ8GK7+irc2fVK6QFIcC8GOnH3zI/v481n+j1L0WaPVvKxwesnY93fEfH++sus2rQ== dependencies: "@babel/runtime" "^7.10.2" use-composed-ref "^1.0.0" From 90a062e9a33308bde8833016beac6198fdc5bf4c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 13:17:48 -0500 Subject: [PATCH 02/19] Remove unused navigation-bar code --- app/soapbox/features/ui/index.js | 3 +- app/styles/application.scss | 1 - app/styles/components/columns.scss | 1 - app/styles/components/navigation-bar.scss | 138 ---------------------- app/styles/rtl.scss | 5 - 5 files changed, 1 insertion(+), 147 deletions(-) delete mode 100644 app/styles/components/navigation-bar.scss diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index d972cb4e4..f4a8ef051 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -656,7 +656,7 @@ class UI extends React.PureComponent { render() { const { streamingUrl } = this.props; const { draggingOver, mobile } = this.state; - const { intl, children, isComposing, location, dropdownMenuIsOpen, me, layouts } = this.props; + const { intl, children, location, dropdownMenuIsOpen, me, layouts } = this.props; if (me === null || !streamingUrl) return null; @@ -692,7 +692,6 @@ class UI extends React.PureComponent { const floatingActionButton = this.shouldHideFAB() ? null : fabElem; const classnames = classNames('ui', { - 'is-composing': isComposing, 'ui--chatroom': this.isChatRoomLocation(), }); diff --git a/app/styles/application.scss b/app/styles/application.scss index a8cf79ba9..611eef9c4 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -56,7 +56,6 @@ @import 'components/search'; @import 'components/react-toggle'; @import 'components/getting-started'; -@import 'components/navigation-bar'; @import 'components/promo-panel'; @import 'components/drawer'; @import 'components/still-image'; diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss index a48fb87d5..0beae81af 100644 --- a/app/styles/components/columns.scss +++ b/app/styles/components/columns.scss @@ -128,7 +128,6 @@ } .account__header__bar { padding: 5px 10px; } - .navigation-bar, .compose-form { padding: 15px; } diff --git a/app/styles/components/navigation-bar.scss b/app/styles/components/navigation-bar.scss deleted file mode 100644 index ebe402970..000000000 --- a/app/styles/components/navigation-bar.scss +++ /dev/null @@ -1,138 +0,0 @@ -.navigation-bar { - padding: 10px; - display: flex; - align-items: center; - flex-shrink: 0; - cursor: default; - color: var(--primary-text-color--faint); - - strong { - color: var(--primary-text-color--faint); - } - - a { - color: inherit; - } - - .permalink { - text-decoration: none; - } - - .navigation-bar__actions { - position: relative; - - .icon-button.close { - position: absolute; - pointer-events: none; - transform: scale(0, 1) translate(-100%, 0); - opacity: 0; - } - - .compose__action-bar .icon-button { - pointer-events: auto; - transform: scale(1, 1) translate(0, 0); - opacity: 1; - } - } -} - -.navigation-bar__profile { - flex: 1 1 auto; - margin-left: 8px; - line-height: 20px; - margin-top: -1px; - overflow: hidden; -} - -.navigation-bar__profile-account { - display: block; - font-weight: 500; - overflow: hidden; - text-overflow: ellipsis; -} - -.navigation-bar__profile-edit { - color: inherit; - text-decoration: none; -} - -@media screen and (max-width: 630px) and (max-height: 400px) { - $duration: 400ms; - $delay: 100ms; - - .tabs-bar, - .search { - will-change: margin-top; - transition: margin-top $duration $delay; - } - - .navigation-bar { - will-change: padding-bottom; - transition: padding-bottom $duration $delay; - } - - .navigation-bar { - & > a:first-child { - will-change: margin-top, margin-left, margin-right, width; - transition: margin-top $duration $delay, margin-left $duration ($duration + $delay), margin-right $duration ($duration + $delay); - } - - & > .navigation-bar__profile-edit { - will-change: margin-top; - transition: margin-top $duration $delay; - } - - .navigation-bar__actions { - & > .icon-button.close { - will-change: opacity transform; - transition: opacity $duration * 0.5 $delay, - transform $duration $delay; - } - - & > .compose__action-bar .icon-button { - will-change: opacity transform; - transition: opacity $duration * 0.5 $delay + $duration * 0.5, - transform $duration $delay; - } - } - } - - .is-composing { - .tabs-bar, - .search { - margin-top: -50px; - } - - .navigation-bar { - padding-bottom: 0; - - & > a:first-child { - margin: -100px 10px 0 -50px; - } - - .navigation-bar__profile { - padding-top: 2px; - } - - .navigation-bar__profile-edit { - position: absolute; - margin-top: -60px; - } - - .navigation-bar__actions { - .icon-button.close { - pointer-events: auto; - opacity: 1; - transform: scale(1, 1) translate(0, 0); - bottom: 5px; - } - - .compose__action-bar .icon-button { - pointer-events: none; - opacity: 0; - transform: scale(0, 1) translate(100%, 0); - } - } - } - } -} diff --git a/app/styles/rtl.scss b/app/styles/rtl.scss index d1351b7f1..051d0e82d 100644 --- a/app/styles/rtl.scss +++ b/app/styles/rtl.scss @@ -18,11 +18,6 @@ body.rtl { margin-left: 4px; } - .navigation-bar__profile { - margin-left: 0; - margin-right: 8px; - } - .search__input { padding-right: 10px; padding-left: 30px; From e87affbbca30ab381e8f43f19b2b408618e37bd2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 13:19:39 -0500 Subject: [PATCH 03/19] Remove 'beforeunload' event from UI This only works in MS Edge but likely causes re-rendering of the UI on compose events in every browser. There are better ways to prevent data loss, like storing the composer state in localStorage. --- app/soapbox/features/ui/index.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index f4a8ef051..aef663b39 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -157,9 +157,6 @@ const mapStateToProps = state => { const account = state.getIn(['accounts', me]); return { - isComposing: state.getIn(['compose', 'is_composing']), - hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0, - hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null, accessToken: getAccessToken(state), streamingUrl: state.getIn(['instance', 'urls', 'streaming_api']), @@ -339,9 +336,6 @@ class UI extends React.PureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, children: PropTypes.node, - isComposing: PropTypes.bool, - hasComposingText: PropTypes.bool, - hasMediaAttachments: PropTypes.bool, location: PropTypes.object, intl: PropTypes.object.isRequired, dropdownMenuIsOpen: PropTypes.bool, @@ -356,17 +350,6 @@ class UI extends React.PureComponent { mobile: isMobile(window.innerWidth), }; - handleBeforeUnload = (e) => { - const { intl, isComposing, hasComposingText, hasMediaAttachments } = this.props; - - if (isComposing && (hasComposingText || hasMediaAttachments)) { - // Setting returnValue to any string causes confirmation dialog. - // Many browsers no longer display this text to users, - // but we set user-friendly message for other browsers, e.g. Edge. - e.returnValue = intl.formatMessage(messages.beforeUnload); - } - } - handleLayoutChange = () => { // The cached heights are no longer accurate, invalidate this.props.dispatch(clearHeight()); @@ -471,9 +454,8 @@ class UI extends React.PureComponent { componentDidMount() { const { account } = this.props; if (!account) return; - window.addEventListener('beforeunload', this.handleBeforeUnload, false); - window.addEventListener('resize', this.handleResize, { passive: true }); + window.addEventListener('resize', this.handleResize, { passive: true }); document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragover', this.handleDragOver, false); document.addEventListener('drop', this.handleDrop, false); @@ -515,7 +497,6 @@ class UI extends React.PureComponent { } componentWillUnmount() { - window.removeEventListener('beforeunload', this.handleBeforeUnload); window.removeEventListener('resize', this.handleResize); document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragover', this.handleDragOver); From 64b0fa6d9962131c0431e4c6a7dec7a4f329b786 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 13:43:14 -0500 Subject: [PATCH 04/19] Load ChatPanes asynchronously --- app/soapbox/features/ui/index.js | 9 +++++++-- app/soapbox/features/ui/util/async-components.js | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index aef663b39..869aae0f2 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -23,6 +23,7 @@ import { openModal } from '../../actions/modal'; import { fetchFollowRequests } from '../../actions/accounts'; import { fetchScheduledStatuses } from '../../actions/scheduled_statuses'; import { WrappedRoute } from './util/react_router_helpers'; +import BundleContainer from './containers/bundle_container'; import UploadArea from './components/upload_area'; import TabsBar from './components/tabs_bar'; import LinkFooter from './components/link_footer'; @@ -44,7 +45,6 @@ import { connectUserStream } from '../../actions/streaming'; import { Redirect } from 'react-router-dom'; import Icon from 'soapbox/components/icon'; import { isStaff } from 'soapbox/utils/accounts'; -import ChatPanes from 'soapbox/features/chats/components/chat_panes'; import ProfileHoverCard from 'soapbox/components/profile_hover_card'; import { getAccessToken } from 'soapbox/utils/auth'; import { getSoapboxConfig } from 'soapbox/actions/soapbox'; @@ -95,6 +95,7 @@ import { MfaForm, ChatIndex, ChatRoom, + ChatPanes, ServerInfo, Dashboard, AwaitingApproval, @@ -695,7 +696,11 @@ class UI extends React.PureComponent { {me && } - {me && !mobile && } + {me && !mobile && ( + + {Component => } + + )} diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index c3c5dc9d7..9d1fcae1d 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -210,6 +210,10 @@ export function ChatRoom() { return import(/* webpackChunkName: "features/chats/chat_room" */'../../chats/chat_room'); } +export function ChatPanes() { + return import(/* webpackChunkName: "features/chats/components/chat_panes" */'../../chats/components/chat_panes'); +} + export function ServerInfo() { return import(/* webpackChunkName: "features/server_info" */'../../server_info'); } From 069f32c602a5dad22e54d060ccd5fb40bf681d08 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 15:42:10 -0500 Subject: [PATCH 05/19] Chats: fix unnecessary re-rendering --- app/soapbox/features/chats/components/chat_panes.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/soapbox/features/chats/components/chat_panes.js b/app/soapbox/features/chats/components/chat_panes.js index 46e3e4260..1f17c2961 100644 --- a/app/soapbox/features/chats/components/chat_panes.js +++ b/app/soapbox/features/chats/components/chat_panes.js @@ -2,7 +2,6 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { getSettings } from 'soapbox/actions/settings'; import ChatList from './chat_list'; @@ -36,12 +35,10 @@ const mapStateToProps = state => { }; export default @connect(mapStateToProps) -@injectIntl class ChatPanes extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, panesData: ImmutablePropTypes.map, } From c5672806cb1c7f6c5a31ece0807e47097d7b7543 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 15:48:10 -0500 Subject: [PATCH 06/19] Add "Why Did You Render" for debugging --- app/soapbox/main.js | 1 + app/soapbox/wdyr.js | 6 ++++++ package.json | 1 + yarn.lock | 12 ++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 app/soapbox/wdyr.js diff --git a/app/soapbox/main.js b/app/soapbox/main.js index 9480a1ea4..8ba4b044e 100644 --- a/app/soapbox/main.js +++ b/app/soapbox/main.js @@ -1,5 +1,6 @@ 'use strict'; +import './wdyr'; import * as registerPushNotifications from './actions/push_notifications'; import { default as Soapbox, store } from './containers/soapbox'; import React from 'react'; diff --git a/app/soapbox/wdyr.js b/app/soapbox/wdyr.js new file mode 100644 index 000000000..978e5f116 --- /dev/null +++ b/app/soapbox/wdyr.js @@ -0,0 +1,6 @@ +import React from 'react'; + +if (process.env.NODE_ENV === 'development') { + const whyDidYouRender = require('@welldone-software/why-did-you-render'); + whyDidYouRender(React); +} diff --git a/package.json b/package.json index 6d5053cf0..55749e31d 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@babel/preset-react": "^7.0.0", "@babel/runtime": "^7.3.4", "@popperjs/core": "^2.4.4", + "@welldone-software/why-did-you-render": "^6.2.0", "array-includes": "^3.0.3", "autoprefixer": "^10.0.0", "axios": "^0.21.0", diff --git a/yarn.lock b/yarn.lock index 64ed758b1..967d0bc3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2135,6 +2135,13 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" +"@welldone-software/why-did-you-render@^6.2.0": + version "6.2.0" + resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-6.2.0.tgz#a053e63f45adb57161c723dee4b005769ea1b64f" + integrity sha512-ViwaE09Vgb0yXzyZuGTWCmWy/nBRAEGyztMdFYuxIgmL8yoXX5TVMCfieiJGdRQQPiDUznlYmcu0lu8kN1lwtQ== + dependencies: + lodash "^4" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -7768,6 +7775,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash@^4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + lodash@^4.0.1: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" From 968c7332f0d2c61b00464b6c3b19fdd315cd6b21 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 16:10:03 -0500 Subject: [PATCH 07/19] Composer: only injectIntl on the ComposeFormContainer for performance --- app/soapbox/features/compose/components/compose_form.js | 5 ++--- .../features/compose/containers/compose_form_container.js | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/compose/components/compose_form.js b/app/soapbox/features/compose/components/compose_form.js index c6a17b2c2..502fbd7f8 100644 --- a/app/soapbox/features/compose/components/compose_form.js +++ b/app/soapbox/features/compose/components/compose_form.js @@ -10,7 +10,7 @@ import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestInput from '../../../components/autosuggest_input'; import PollButtonContainer from '../containers/poll_button_container'; import UploadButtonContainer from '../containers/upload_button_container'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import MarkdownButtonContainer from '../containers/markdown_button_container'; import ScheduleFormContainer from '../containers/schedule_form_container'; @@ -38,8 +38,7 @@ const messages = defineMessages({ schedule: { id: 'compose_form.schedule', defaultMessage: 'Schedule' }, }); -export default @injectIntl -class ComposeForm extends ImmutablePureComponent { +export default class ComposeForm extends ImmutablePureComponent { state = { composeFocused: false, diff --git a/app/soapbox/features/compose/containers/compose_form_container.js b/app/soapbox/features/compose/containers/compose_form_container.js index 874402b62..a4ecff6c8 100644 --- a/app/soapbox/features/compose/containers/compose_form_container.js +++ b/app/soapbox/features/compose/containers/compose_form_container.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; import ComposeForm from '../components/compose_form'; import { changeCompose, @@ -73,4 +74,4 @@ function mergeProps(stateProps, dispatchProps, ownProps) { }); } -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm)); From 74e6d8ce81b0359ae97a58045e4d1a80198eba19 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 16:43:28 -0500 Subject: [PATCH 08/19] Refactor ThemeToggle, SettingToggle for performance --- .../components/setting_toggle.js | 14 +++++-- .../features/ui/components/theme_toggle.js | 37 ++++++++++--------- .../ui/components/theme_toggle_container.js | 5 ++- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/soapbox/features/notifications/components/setting_toggle.js b/app/soapbox/features/notifications/components/setting_toggle.js index 145e2b03a..c9e649b6c 100644 --- a/app/soapbox/features/notifications/components/setting_toggle.js +++ b/app/soapbox/features/notifications/components/setting_toggle.js @@ -1,9 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import Toggle from 'react-toggle'; -export default class SettingToggle extends React.PureComponent { +export default class SettingToggle extends ImmutablePureComponent { static propTypes = { prefix: PropTypes.string, @@ -15,7 +16,6 @@ export default class SettingToggle extends React.PureComponent { PropTypes.bool, PropTypes.object, ]), - condition: PropTypes.string, ariaLabel: PropTypes.string, } @@ -24,12 +24,18 @@ export default class SettingToggle extends React.PureComponent { } render() { - const { prefix, settings, settingPath, label, icons, condition, ariaLabel } = this.props; + const { prefix, settings, settingPath, label, icons, ariaLabel } = this.props; const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-'); return (
- + {label && ()}
); diff --git a/app/soapbox/features/ui/components/theme_toggle.js b/app/soapbox/features/ui/components/theme_toggle.js index 317020111..d1b706a0f 100644 --- a/app/soapbox/features/ui/components/theme_toggle.js +++ b/app/soapbox/features/ui/components/theme_toggle.js @@ -1,44 +1,45 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, defineMessages } from 'react-intl'; -import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; import Icon from '../../../components/icon'; -import SettingToggle from '../../notifications/components/setting_toggle'; +import Toggle from 'react-toggle'; const messages = defineMessages({ switchToLight: { id: 'tabs_bar.theme_toggle_light', defaultMessage: 'Switch to light theme' }, switchToDark: { id: 'tabs_bar.theme_toggle_dark', defaultMessage: 'Switch to dark theme' }, }); -export default @injectIntl -class ThemeToggle extends React.PureComponent { +export default class ThemeToggle extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, - settings: ImmutablePropTypes.map.isRequired, + themeMode: PropTypes.string.isRequired, onToggle: PropTypes.func.isRequired, showLabel: PropTypes.bool, }; handleToggleTheme = () => { - this.props.onToggle(this.props.settings.get('themeMode') === 'light' ? 'dark' : 'light'); + this.props.onToggle(this.props.themeMode === 'light' ? 'dark' : 'light'); } render() { - const { intl, settings, showLabel } = this.props; - let toggle = ( - , unchecked: }} ariaLabel={settings.get('themeMode') === 'light' ? intl.formatMessage(messages.switchToDark) : intl.formatMessage(messages.switchToLight)} /> - ); - - if (showLabel) { - toggle = ( - , unchecked: }} label={settings.get('themeMode') === 'light' ? intl.formatMessage(messages.switchToDark) : intl.formatMessage(messages.switchToLight)} /> - ); - } + const { intl, themeMode, showLabel } = this.props; + const id ='theme-toggle'; + const label = intl.formatMessage(themeMode === 'light' ? messages.switchToDark : messages.switchToLight); return (
- {toggle} +
+ , unchecked: }} + onKeyDown={this.onKeyDown} + /> + {showLabel && ()} +
); } diff --git a/app/soapbox/features/ui/components/theme_toggle_container.js b/app/soapbox/features/ui/components/theme_toggle_container.js index 4a2cad1f7..f9b1d5b73 100644 --- a/app/soapbox/features/ui/components/theme_toggle_container.js +++ b/app/soapbox/features/ui/components/theme_toggle_container.js @@ -1,10 +1,11 @@ import { connect } from 'react-redux'; import { changeSetting, getSettings } from 'soapbox/actions/settings'; +import { injectIntl } from 'react-intl'; import ThemeToggle from './theme_toggle'; const mapStateToProps = state => { return { - settings: getSettings(state), + themeMode: getSettings(state).get('themeMode'), }; }; @@ -14,4 +15,4 @@ const mapDispatchToProps = (dispatch) => ({ }, }); -export default connect(mapStateToProps, mapDispatchToProps)(ThemeToggle); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ThemeToggle)); From 799f19bbc4f1f41d231c7b40d414a96d545bd364 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 1 Jul 2021 18:01:33 -0500 Subject: [PATCH 09/19] Chats: improve performance --- app/soapbox/features/chats/components/chat.js | 23 ++++++++++- .../features/chats/components/chat_list.js | 23 ++++++----- .../features/chats/components/chat_panes.js | 39 +++++++------------ .../features/chats/components/chat_window.js | 38 +++++++++++------- 4 files changed, 72 insertions(+), 51 deletions(-) diff --git a/app/soapbox/features/chats/components/chat.js b/app/soapbox/features/chats/components/chat.js index 1a09edd47..8db6ddb62 100644 --- a/app/soapbox/features/chats/components/chat.js +++ b/app/soapbox/features/chats/components/chat.js @@ -1,4 +1,5 @@ import React from 'react'; +import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Avatar from '../../../components/avatar'; @@ -6,11 +7,29 @@ import DisplayName from '../../../components/display_name'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { shortNumberFormat } from 'soapbox/utils/numbers'; import emojify from 'soapbox/features/emoji/emoji'; +import { makeGetChat } from 'soapbox/selectors'; -export default class Chat extends ImmutablePureComponent { + +const makeMapStateToProps = () => { + const getChat = makeGetChat(); + + const mapStateToProps = (state, { chatId }) => { + const chat = state.getIn(['chats', chatId]); + + return { + chat: chat ? getChat(state, chat.toJS()) : undefined, + }; + }; + + return mapStateToProps; +}; + +export default @connect(makeMapStateToProps) +class Chat extends ImmutablePureComponent { static propTypes = { - chat: ImmutablePropTypes.map.isRequired, + chatId: PropTypes.string.isRequired, + chat: ImmutablePropTypes.map, onClick: PropTypes.func, }; diff --git a/app/soapbox/features/chats/components/chat_list.js b/app/soapbox/features/chats/components/chat_list.js index e24d31ab9..2624a6e2f 100644 --- a/app/soapbox/features/chats/components/chat_list.js +++ b/app/soapbox/features/chats/components/chat_list.js @@ -1,10 +1,10 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Chat from './chat'; -import { makeGetChat } from 'soapbox/selectors'; const chatDateComparator = (chatA, chatB) => { // Sort most recently updated chats at the top @@ -18,15 +18,13 @@ const chatDateComparator = (chatA, chatB) => { }; const mapStateToProps = state => { - const getChat = makeGetChat(); - - const chats = state.get('chats') - .map(chat => getChat(state, chat.toJS())) + const chatIds = state.get('chats') .toList() - .sort(chatDateComparator); + .sort(chatDateComparator) + .map(chat => chat.get('id')); return { - chats, + chatIds, }; }; @@ -37,23 +35,24 @@ class ChatList extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + chatIds: ImmutablePropTypes.list, onClickChat: PropTypes.func, emptyMessage: PropTypes.node, }; render() { - const { chats, emptyMessage } = this.props; + const { chatIds, emptyMessage } = this.props; return (
- {chats.count() === 0 && + {chatIds.count() === 0 &&
{emptyMessage}
} - {chats.map(chat => ( -
+ {chatIds.map(chatId => ( +
diff --git a/app/soapbox/features/chats/components/chat_panes.js b/app/soapbox/features/chats/components/chat_panes.js index 1f17c2961..421135c34 100644 --- a/app/soapbox/features/chats/components/chat_panes.js +++ b/app/soapbox/features/chats/components/chat_panes.js @@ -6,30 +6,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { getSettings } from 'soapbox/actions/settings'; import ChatList from './chat_list'; import { FormattedMessage } from 'react-intl'; -import { makeGetChat } from 'soapbox/selectors'; import { openChat, toggleMainWindow } from 'soapbox/actions/chats'; import ChatWindow from './chat_window'; import { shortNumberFormat } from 'soapbox/utils/numbers'; import AudioToggle from 'soapbox/features/chats/components/audio_toggle'; -import { List as ImmutableList } from 'immutable'; - -const addChatsToPanes = (state, panesData) => { - const getChat = makeGetChat(); - - const newPanes = panesData.get('panes').reduce((acc, pane) => { - const chat = getChat(state, { id: pane.get('chat_id') }); - if (!chat) return acc; - return acc.push(pane.set('chat', chat)); - }, ImmutableList()); - - return panesData.set('panes', newPanes); -}; const mapStateToProps = state => { - const panesData = getSettings(state).get('chats'); + const settings = getSettings(state); return { - panesData: addChatsToPanes(state, panesData), + panes: settings.getIn(['chats', 'panes']), + mainWindowState: settings.getIn(['chats', 'mainWindow']), unreadCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), }; }; @@ -39,7 +26,8 @@ class ChatPanes extends ImmutablePureComponent { static propTypes = { dispatch: PropTypes.func.isRequired, - panesData: ImmutablePropTypes.map, + mainWindowState: PropTypes.string, + panes: ImmutablePropTypes.list, } handleClickChat = (chat) => { @@ -51,12 +39,10 @@ class ChatPanes extends ImmutablePureComponent { } render() { - const { panesData, unreadCount } = this.props; - const panes = panesData.get('panes'); - const mainWindow = panesData.get('mainWindow'); + const { panes, mainWindowState, unreadCount } = this.props; const mainWindowPane = ( -
+
{unreadCount > 0 && {shortNumberFormat(unreadCount)}}