From 7232feecf710b1aa85b5f479cead510359582294 Mon Sep 17 00:00:00 2001 From: M-ZubairAhmed Date: Wed, 4 Sep 2024 05:55:47 +0000 Subject: [PATCH] [MM-59299] Investigate further breaking down chunks of components/node_modules for initial load (#27845) --- .../file_upload_overlay.test.tsx.snap | 6 + .../advanced_text_editor.tsx | 6 +- webapp/channels/src/components/app.tsx | 4 +- .../center_channel/center_channel.tsx | 35 ++-- .../channel_layout/channel_controller.tsx | 8 +- .../__snapshots__/channel_view.test.tsx.snap | 8 +- .../components/channel_view/channel_view.tsx | 12 +- .../src/components/file_upload_overlay.tsx | 2 + .../src/components/logged_in_route/index.tsx | 40 ++++ webapp/channels/src/components/root/root.tsx | 172 +++++++----------- .../__snapshots__/sidebar.test.tsx.snap | 2 +- .../src/components/sidebar/sidebar.test.tsx | 26 ++- .../src/components/sidebar/sidebar.tsx | 38 ++-- .../__snapshots__/sidebar_list.test.tsx.snap | 2 +- .../sidebar/sidebar_list/sidebar_list.tsx | 35 ++-- .../constant.ts | 0 .../index.ts | 4 +- .../sidebar_mobile_right_menu.tsx} | 0 .../element_identification.test.tsx | 4 +- 19 files changed, 213 insertions(+), 191 deletions(-) create mode 100644 webapp/channels/src/components/logged_in_route/index.tsx rename webapp/channels/src/components/{sidebar_right_menu => sidebar_mobile_right_menu}/constant.ts (100%) rename webapp/channels/src/components/{sidebar_right_menu => sidebar_mobile_right_menu}/index.ts (86%) rename webapp/channels/src/components/{sidebar_right_menu/sidebar_right_menu.tsx => sidebar_mobile_right_menu/sidebar_mobile_right_menu.tsx} (100%) diff --git a/webapp/channels/src/components/__snapshots__/file_upload_overlay.test.tsx.snap b/webapp/channels/src/components/__snapshots__/file_upload_overlay.test.tsx.snap index 3f340387c4..c816b92dad 100644 --- a/webapp/channels/src/components/__snapshots__/file_upload_overlay.test.tsx.snap +++ b/webapp/channels/src/components/__snapshots__/file_upload_overlay.test.tsx.snap @@ -13,6 +13,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Files @@ -28,6 +29,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Logo @@ -48,6 +50,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Files @@ -63,6 +66,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Logo @@ -83,6 +87,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Files @@ -98,6 +103,7 @@ exports[`components/FileUploadOverlay should match snapshot when file upload is Logo diff --git a/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx b/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx index 6265d70cbf..76cceda78f 100644 --- a/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx +++ b/webapp/channels/src/components/advanced_text_editor/advanced_text_editor.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import classNames from 'classnames'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {lazy, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; @@ -24,9 +24,9 @@ import {makeGetDraft} from 'selectors/rhs'; import {connectionErrorCount} from 'selectors/views/system'; import LocalStorageStore from 'stores/local_storage_store'; +import {makeAsyncComponent} from 'components/async_load'; import AutoHeightSwitcher from 'components/common/auto_height_switcher'; import useDidUpdate from 'components/common/hooks/useDidUpdate'; -import FileLimitStickyBanner from 'components/file_limit_sticky_banner'; import MessageSubmitError from 'components/message_submit_error'; import MsgTyping from 'components/msg_typing'; import RhsSuggestionList from 'components/suggestion/rhs_suggestion_list'; @@ -66,6 +66,8 @@ import useUploadFiles from './use_upload_files'; import './advanced_text_editor.scss'; +const FileLimitStickyBanner = makeAsyncComponent('FileLimitStickyBanner', lazy(() => import('components/file_limit_sticky_banner'))); + function isDraftEmpty(draft: PostDraft) { return draft.message === '' && draft.fileInfos.length === 0 && draft.uploadsInProgress.length === 0; } diff --git a/webapp/channels/src/components/app.tsx b/webapp/channels/src/components/app.tsx index fdf1d8a0f7..9750cce11d 100644 --- a/webapp/channels/src/components/app.tsx +++ b/webapp/channels/src/components/app.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, {lazy} from 'react'; import {hot} from 'react-hot-loader/root'; import {Provider} from 'react-redux'; import {Router} from 'react-router-dom'; @@ -11,7 +11,7 @@ import store from 'stores/redux_store'; import {makeAsyncComponent} from 'components/async_load'; import {getHistory} from 'utils/browser_history'; -const LazyRoot = React.lazy(() => import('components/root')); +const LazyRoot = lazy(() => import('components/root')); const Root = makeAsyncComponent('Root', LazyRoot); diff --git a/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx b/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx index e7fdf573d9..cf4de85022 100644 --- a/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx +++ b/webapp/channels/src/components/channel_layout/center_channel/center_channel.tsx @@ -2,43 +2,34 @@ // See LICENSE.txt for license information. import classNames from 'classnames'; -import React from 'react'; +import React, {lazy} from 'react'; import {Route, Switch, Redirect} from 'react-router-dom'; import {makeAsyncComponent} from 'components/async_load'; import ChannelIdentifierRouter from 'components/channel_layout/channel_identifier_router'; -import PlaybookRunner from 'components/channel_layout/playbook_runner'; import LoadingScreen from 'components/loading_screen'; -import PermalinkView from 'components/permalink_view'; import {IDENTIFIER_PATH_PATTERN, ID_PATH_PATTERN, TEAM_NAME_PATH_PATTERN} from 'utils/path'; import type {OwnProps, PropsFromRedux} from './index'; -const LazyChannelHeaderMobile = makeAsyncComponent( - 'LazyChannelHeaderMobile', - React.lazy(() => import('components/channel_header_mobile')), -); - -const LazyGlobalThreads = makeAsyncComponent( - 'LazyGlobalThreads', - React.lazy(() => import('components/threading/global_threads')), +const ChannelHeaderMobile = makeAsyncComponent('ChannelHeaderMobile', lazy(() => import('components/channel_header_mobile'))); +const GlobalThreads = makeAsyncComponent('GlobalThreads', lazy(() => import('components/threading/global_threads')), (
), ); - -const LazyDrafts = makeAsyncComponent( - 'LazyDrafts', - React.lazy(() => import('components/drafts')), +const Drafts = makeAsyncComponent('Drafts', lazy(() => import('components/drafts')), (
), ); +const PermalinkView = makeAsyncComponent('PermalinkView', lazy(() => import('components/permalink_view'))); +const PlaybookRunner = makeAsyncComponent('PlaybookRunner', lazy(() => import('components/channel_layout/playbook_runner'))); type Props = PropsFromRedux & OwnProps; @@ -85,11 +76,13 @@ export default class CenterChannel extends React.PureComponent { })} > {isMobileView && ( -
- + )}
@@ -114,12 +107,12 @@ export default class CenterChannel extends React.PureComponent { {isCollapsedThreadsEnabled ? ( ) : null} diff --git a/webapp/channels/src/components/channel_layout/channel_controller.tsx b/webapp/channels/src/components/channel_layout/channel_controller.tsx index dca76f9de6..a1243f3965 100644 --- a/webapp/channels/src/components/channel_layout/channel_controller.tsx +++ b/webapp/channels/src/components/channel_layout/channel_controller.tsx @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import classNames from 'classnames'; -import React, {useEffect} from 'react'; +import React, {lazy, useEffect} from 'react'; import {useDispatch, useSelector} from 'react-redux'; import {cleanUpStatusAndProfileFetchingPoll} from 'mattermost-redux/actions/status_profile_polling'; @@ -10,10 +10,9 @@ import {getIsUserStatusesConfigEnabled} from 'mattermost-redux/selectors/entitie import {addVisibleUsersInCurrentChannelToStatusPoll} from 'actions/status_actions'; +import {makeAsyncComponent} from 'components/async_load'; import CenterChannel from 'components/channel_layout/center_channel'; import LoadingScreen from 'components/loading_screen'; -import ProductNoticesModal from 'components/product_notices_modal'; -import ResetStatusModal from 'components/reset_status_modal'; import Sidebar from 'components/sidebar'; import CRTPostsChannelResetWatcher from 'components/threading/channel_threads/posts_channel_reset_watcher'; import UnreadsStatusHandler from 'components/unreads_status_handler'; @@ -22,6 +21,9 @@ import Pluggable from 'plugins/pluggable'; import {Constants} from 'utils/constants'; import {isInternetExplorer, isEdge} from 'utils/user_agent'; +const ProductNoticesModal = makeAsyncComponent('ProductNoticesModal', lazy(() => import('components/product_notices_modal'))); +const ResetStatusModal = makeAsyncComponent('ResetStatusModal', lazy(() => import('components/reset_status_modal'))); + const BODY_CLASS_FOR_CHANNEL = ['app__body', 'channel-view']; type Props = { diff --git a/webapp/channels/src/components/channel_view/__snapshots__/channel_view.test.tsx.snap b/webapp/channels/src/components/channel_view/__snapshots__/channel_view.test.tsx.snap index fe543c79ec..949b3f6838 100644 --- a/webapp/channels/src/components/channel_view/__snapshots__/channel_view.test.tsx.snap +++ b/webapp/channels/src/components/channel_view/__snapshots__/channel_view.test.tsx.snap @@ -8,7 +8,7 @@ exports[`components/channel_view Should match snapshot if channel is archived 1` - - - - +
`; diff --git a/webapp/channels/src/components/channel_view/channel_view.tsx b/webapp/channels/src/components/channel_view/channel_view.tsx index 733ae7562e..176d2f35ff 100644 --- a/webapp/channels/src/components/channel_view/channel_view.tsx +++ b/webapp/channels/src/components/channel_view/channel_view.tsx @@ -1,15 +1,12 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react'; +import React, {lazy} from 'react'; import {FormattedMessage} from 'react-intl'; import type {RouteComponentProps} from 'react-router-dom'; -import AdvancedCreatePost from 'components/advanced_create_post'; -import ChannelBookmarks from 'components/channel_bookmarks'; -import ChannelHeader from 'components/channel_header'; +import {makeAsyncComponent} from 'components/async_load'; import deferComponentRender from 'components/deferComponentRender'; -import FileUploadOverlay from 'components/file_upload_overlay'; import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import PostView from 'components/post_view'; @@ -17,6 +14,11 @@ import WebSocketClient from 'client/web_websocket_client'; import type {PropsFromRedux} from './index'; +const ChannelHeader = makeAsyncComponent('ChannelHeader', lazy(() => import('components/channel_header'))); +const FileUploadOverlay = makeAsyncComponent('FileUploadOverlay', lazy(() => import('components/file_upload_overlay'))); +const ChannelBookmarks = makeAsyncComponent('ChannelBookmarks', lazy(() => import('components/channel_bookmarks'))); +const AdvancedCreatePost = makeAsyncComponent('AdvancedCreatePost', lazy(() => import('components/advanced_create_post'))); + export type Props = PropsFromRedux & RouteComponentProps<{ postid?: string; }>; diff --git a/webapp/channels/src/components/file_upload_overlay.tsx b/webapp/channels/src/components/file_upload_overlay.tsx index 6792ebb61e..f32afd821a 100644 --- a/webapp/channels/src/components/file_upload_overlay.tsx +++ b/webapp/channels/src/components/file_upload_overlay.tsx @@ -29,6 +29,7 @@ const FileUploadOverlay = (props: Props) => { className='overlay__files' src={fileOverlayImage} alt='Files' + loading='lazy' /> { className='overlay__logo' src={overlayLogoImage} alt='Logo' + loading='lazy' /> diff --git a/webapp/channels/src/components/logged_in_route/index.tsx b/webapp/channels/src/components/logged_in_route/index.tsx new file mode 100644 index 0000000000..d8782dfeb2 --- /dev/null +++ b/webapp/channels/src/components/logged_in_route/index.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {lazy} from 'react'; +import type {RouteComponentProps} from 'react-router-dom'; +import {Route} from 'react-router-dom'; + +import type {Theme} from 'mattermost-redux/selectors/entities/preferences'; + +import {makeAsyncComponent} from 'components/async_load'; +import CompassThemeProvider from 'components/compass_theme_provider/compass_theme_provider'; +import LoggedIn from 'components/logged_in'; + +const OnBoardingTaskList = makeAsyncComponent('OnboardingTaskList', lazy(() => import('components/onboarding_tasklist'))); + +type Props = { + component: React.ComponentType>; + path: string | string[]; + theme?: Theme; // the routes that send the theme are the ones that will actually need to show the onboarding tasklist +}; + +export default function LoggedInRoute(props: Props) { + const {component: Component, theme, ...rest} = props; + + return ( + ( + + {theme && ( + + + + )} + + + )} + /> + ); +} diff --git a/webapp/channels/src/components/root/root.tsx b/webapp/channels/src/components/root/root.tsx index 22d4f709b5..e13e8f709d 100644 --- a/webapp/channels/src/components/root/root.tsx +++ b/webapp/channels/src/components/root/root.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import deepEqual from 'fast-deep-equal'; -import React from 'react'; +import React, {lazy} from 'react'; import {Route, Switch, Redirect} from 'react-router-dom'; import type {RouteComponentProps} from 'react-router-dom'; @@ -13,42 +13,31 @@ import {setSystemEmojis} from 'mattermost-redux/actions/emojis'; import {setUrl} from 'mattermost-redux/actions/general'; import {Client4} from 'mattermost-redux/client'; import {rudderAnalytics, RudderTelemetryHandler} from 'mattermost-redux/client/rudder'; -import type {Theme} from 'mattermost-redux/selectors/entities/preferences'; import {measurePageLoadTelemetry, temporarilySetPageLoadContext, trackEvent, trackSelectorMetrics} from 'actions/telemetry_actions.jsx'; import BrowserStore from 'stores/browser_store'; -import AccessProblem from 'components/access_problem'; -import AnnouncementBarController from 'components/announcement_bar'; -import AppBar from 'components/app_bar/app_bar'; import {makeAsyncComponent} from 'components/async_load'; -import CloudEffects from 'components/cloud_effects'; -import CompassThemeProvider from 'components/compass_theme_provider/compass_theme_provider'; import OpenPluginInstallPost from 'components/custom_open_plugin_install_post_renderer'; import GlobalHeader from 'components/global_header/global_header'; import {HFRoute} from 'components/header_footer_route/header_footer_route'; import {HFTRoute, LoggedInHFTRoute} from 'components/header_footer_template_route'; import InitialLoadingScreen from 'components/initial_loading_screen'; -import MobileViewWatcher from 'components/mobile_view_watcher'; -import ModalController from 'components/modal_controller'; -import LaunchingWorkspace, {LAUNCHING_WORKSPACE_FULLSCREEN_Z_INDEX} from 'components/preparing_workspace/launching_workspace'; +import LoggedIn from 'components/logged_in'; +import LoggedInRoute from 'components/logged_in_route'; +import {LAUNCHING_WORKSPACE_FULLSCREEN_Z_INDEX} from 'components/preparing_workspace/launching_workspace'; import {Animations} from 'components/preparing_workspace/steps'; -import SidebarRight from 'components/sidebar_right'; -import SidebarRightMenu from 'components/sidebar_right_menu'; -import SystemNotice from 'components/system_notice'; -import TeamSidebar from 'components/team_sidebar'; -import WindowSizeObserver from 'components/window_size_observer/WindowSizeObserver'; +import SidebarMobileRightMenu from 'components/sidebar_mobile_right_menu'; import webSocketClient from 'client/web_websocket_client'; import {initializePlugins} from 'plugins'; -import Pluggable from 'plugins/pluggable'; import A11yController from 'utils/a11y_controller'; import {PageLoadContext} from 'utils/constants'; import {EmojiIndicesByAlias} from 'utils/emoji'; import {TEAM_NAME_PATH_PATTERN} from 'utils/path'; import {getSiteURL} from 'utils/url'; -import * as UserAgent from 'utils/user_agent'; -import * as Utils from 'utils/utils'; +import {isAndroidWeb, isChromebook, isDesktopApp, isIosWeb} from 'utils/user_agent'; +import {applyTheme, isTextDroppableEvent} from 'utils/utils'; import LuxonController from './luxon_controller'; import PerformanceReporterController from './performance_reporter_controller'; @@ -59,68 +48,36 @@ import type {PropsFromRedux} from './index'; import 'plugins/export.js'; -const LazyErrorPage = React.lazy(() => import('components/error_page')); -const LazyLogin = React.lazy(() => import('components/login/login')); -const LazyAdminConsole = React.lazy(() => import('components/admin_console')); -const LazyLoggedIn = React.lazy(() => import('components/logged_in')); -const LazyPasswordResetSendLink = React.lazy(() => import('components/password_reset_send_link')); -const LazyPasswordResetForm = React.lazy(() => import('components/password_reset_form')); -const LazySignup = React.lazy(() => import('components/signup/signup')); -const LazyTermsOfService = React.lazy(() => import('components/terms_of_service')); -const LazyShouldVerifyEmail = React.lazy(() => import('components/should_verify_email/should_verify_email')); -const LazyDoVerifyEmail = React.lazy(() => import('components/do_verify_email/do_verify_email')); -const LazyClaimController = React.lazy(() => import('components/claim')); -const LazyLinkingLandingPage = React.lazy(() => import('components/linking_landing_page')); -const LazySelectTeam = React.lazy(() => import('components/select_team')); -const LazyAuthorize = React.lazy(() => import('components/authorize')); -const LazyCreateTeam = React.lazy(() => import('components/create_team')); -const LazyMfa = React.lazy(() => import('components/mfa/mfa_controller')); -const LazyPreparingWorkspace = React.lazy(() => import('components/preparing_workspace')); -const LazyTeamController = React.lazy(() => import('components/team_controller')); -const LazyOnBoardingTaskList = React.lazy(() => import('components/onboarding_tasklist')); - -const CreateTeam = makeAsyncComponent('CreateTeam', LazyCreateTeam); -const ErrorPage = makeAsyncComponent('ErrorPage', LazyErrorPage); -const TermsOfService = makeAsyncComponent('TermsOfService', LazyTermsOfService); -const Login = makeAsyncComponent('LoginController', LazyLogin); -const AdminConsole = makeAsyncComponent('AdminConsole', LazyAdminConsole); -const LoggedIn = makeAsyncComponent('LoggedIn', LazyLoggedIn); -const PasswordResetSendLink = makeAsyncComponent('PasswordResedSendLink', LazyPasswordResetSendLink); -const PasswordResetForm = makeAsyncComponent('PasswordResetForm', LazyPasswordResetForm); -const Signup = makeAsyncComponent('SignupController', LazySignup); -const ShouldVerifyEmail = makeAsyncComponent('ShouldVerifyEmail', LazyShouldVerifyEmail); -const DoVerifyEmail = makeAsyncComponent('DoVerifyEmail', LazyDoVerifyEmail); -const ClaimController = makeAsyncComponent('ClaimController', LazyClaimController); -const LinkingLandingPage = makeAsyncComponent('LinkingLandingPage', LazyLinkingLandingPage); -const SelectTeam = makeAsyncComponent('SelectTeam', LazySelectTeam); -const Authorize = makeAsyncComponent('Authorize', LazyAuthorize); -const Mfa = makeAsyncComponent('Mfa', LazyMfa); -const PreparingWorkspace = makeAsyncComponent('PreparingWorkspace', LazyPreparingWorkspace); -const TeamController = makeAsyncComponent('TeamController', LazyTeamController); -const OnBoardingTaskList = makeAsyncComponent('OnboardingTaskList', LazyOnBoardingTaskList); - -type LoggedInRouteProps = { - component: React.ComponentType>; - path: string | string[]; - theme?: Theme; // the routes that send the theme are the ones that will actually need to show the onboarding tasklist -}; - -function LoggedInRoute(props: LoggedInRouteProps) { - const {component: Component, theme, ...rest} = props; - return ( - ( - - {theme && - - } - - - )} - /> - ); -} +const MobileViewWatcher = makeAsyncComponent('MobileViewWatcher', lazy(() => import('components/mobile_view_watcher'))); +const WindowSizeObserver = makeAsyncComponent('WindowSizeObserver', lazy(() => import('components/window_size_observer/WindowSizeObserver'))); +const ErrorPage = makeAsyncComponent('ErrorPage', lazy(() => import('components/error_page'))); +const Login = makeAsyncComponent('LoginController', lazy(() => import('components/login/login'))); +const AccessProblem = makeAsyncComponent('AccessProblem', lazy(() => import('components/access_problem'))); +const PasswordResetSendLink = makeAsyncComponent('PasswordResedSendLink', lazy(() => import('components/password_reset_send_link'))); +const PasswordResetForm = makeAsyncComponent('PasswordResetForm', lazy(() => import('components/password_reset_form'))); +const Signup = makeAsyncComponent('SignupController', lazy(() => import('components/signup/signup'))); +const ShouldVerifyEmail = makeAsyncComponent('ShouldVerifyEmail', lazy(() => import('components/should_verify_email/should_verify_email'))); +const DoVerifyEmail = makeAsyncComponent('DoVerifyEmail', lazy(() => import('components/do_verify_email/do_verify_email'))); +const ClaimController = makeAsyncComponent('ClaimController', lazy(() => import('components/claim'))); +const TermsOfService = makeAsyncComponent('TermsOfService', lazy(() => import('components/terms_of_service'))); +const LinkingLandingPage = makeAsyncComponent('LinkingLandingPage', lazy(() => import('components/linking_landing_page'))); +const AdminConsole = makeAsyncComponent('AdminConsole', lazy(() => import('components/admin_console'))); +const SelectTeam = makeAsyncComponent('SelectTeam', lazy(() => import('components/select_team'))); +const Authorize = makeAsyncComponent('Authorize', lazy(() => import('components/authorize'))); +const CreateTeam = makeAsyncComponent('CreateTeam', lazy(() => import('components/create_team'))); +const Mfa = makeAsyncComponent('Mfa', lazy(() => import('components/mfa/mfa_controller'))); +const PreparingWorkspace = makeAsyncComponent('PreparingWorkspace', lazy(() => import('components/preparing_workspace'))); +const Pluggable = makeAsyncComponent('Pluggable', lazy(() => import('plugins/pluggable'))); +const LaunchingWorkspace = makeAsyncComponent('LaunchingWorkspace', lazy(() => import('components/preparing_workspace/launching_workspace'))); +const CompassThemeProvider = makeAsyncComponent('CompassThemeProvider', lazy(() => import('components/compass_theme_provider/compass_theme_provider'))); +const TeamController = makeAsyncComponent('TeamController', lazy(() => import('components/team_controller'))); +const AnnouncementBarController = makeAsyncComponent('AnnouncementBarController', lazy(() => import('components/announcement_bar'))); +const SystemNotice = makeAsyncComponent('SystemNotice', lazy(() => import('components/system_notice'))); +const CloudEffects = makeAsyncComponent('CloudEffects', lazy(() => import('components/cloud_effects'))); +const TeamSidebar = makeAsyncComponent('TeamSidebar', lazy(() => import('components/team_sidebar'))); +const SidebarRight = makeAsyncComponent('SidebarRight', lazy(() => import('components/sidebar_right'))); +const ModalController = makeAsyncComponent('ModalController', lazy(() => import('components/modal_controller'))); +const AppBar = makeAsyncComponent('AppBar', lazy(() => import('components/app_bar/app_bar'))); const noop = () => {}; @@ -138,7 +95,6 @@ export default class Root extends React.PureComponent { constructor(props: Props) { super(props); - // Redux setUrl(getSiteURL()); // Disable auth header to enable CSRF check @@ -146,24 +102,6 @@ export default class Root extends React.PureComponent { setSystemEmojis(new Set(EmojiIndicesByAlias.keys())); - // Force logout of all tabs if one tab is logged out - window.addEventListener('storage', this.handleLogoutLoginSignal); - - // Prevent drag and drop files from navigating away from the app - document.addEventListener('drop', (e) => { - if (e.dataTransfer && e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind === 'file') { - e.preventDefault(); - e.stopPropagation(); - } - }); - - document.addEventListener('dragover', (e) => { - if (!Utils.isTextDroppableEvent(e) && !document.body.classList.contains('focalboard-body')) { - e.preventDefault(); - e.stopPropagation(); - } - }); - this.state = { shouldMountAppRoutes: false, }; @@ -251,23 +189,23 @@ export default class Root extends React.PureComponent { this.showLandingPageIfNecessary(); - Utils.applyTheme(this.props.theme); + applyTheme(this.props.theme); }; private showLandingPageIfNecessary = () => { // We have nothing to redirect to if we're already on Desktop App // Chromebook has no Desktop App to switch to - if (UserAgent.isDesktopApp() || UserAgent.isChromebook()) { + if (isDesktopApp() || isChromebook()) { return; } // Nothing to link to if we've removed the Android App download link - if (UserAgent.isAndroidWeb() && !this.props.androidDownloadLink) { + if (isAndroidWeb() && !this.props.androidDownloadLink) { return; } // Nothing to link to if we've removed the iOS App download link - if (UserAgent.isIosWeb() && !this.props.iosDownloadLink) { + if (isIosWeb() && !this.props.iosDownloadLink) { return; } @@ -312,7 +250,7 @@ export default class Root extends React.PureComponent { componentDidUpdate(prevProps: Props, prevState: State) { if (!deepEqual(prevProps.theme, this.props.theme)) { - Utils.applyTheme(this.props.theme); + applyTheme(this.props.theme); } if (this.props.location.pathname === '/') { @@ -384,6 +322,20 @@ export default class Root extends React.PureComponent { } }; + handleDropEvent = (e: DragEvent) => { + if (e.dataTransfer && e.dataTransfer.items.length > 0 && e.dataTransfer.items[0].kind === 'file') { + e.preventDefault(); + e.stopPropagation(); + } + }; + + handleDragOverEvent = (e: DragEvent) => { + if (!isTextDroppableEvent(e) && !document.body.classList.contains('focalboard-body')) { + e.preventDefault(); + e.stopPropagation(); + } + }; + componentDidMount() { temporarilySetPageLoadContext(PageLoadContext.PAGE_LOAD); @@ -394,10 +346,20 @@ export default class Root extends React.PureComponent { measurePageLoadTelemetry(); trackSelectorMetrics(); + + // Force logout of all tabs if one tab is logged out + window.addEventListener('storage', this.handleLogoutLoginSignal); + + // Prevent drag and drop files from navigating away from the app + document.addEventListener('drop', this.handleDropEvent); + + document.addEventListener('dragover', this.handleDragOverEvent); } componentWillUnmount() { window.removeEventListener('storage', this.handleLogoutLoginSignal); + document.removeEventListener('drop', this.handleDropEvent); + document.removeEventListener('dragover', this.handleDragOverEvent); } handleLogoutLoginSignal = (e: StorageEvent) => { @@ -600,7 +562,7 @@ export default class Root extends React.PureComponent { - + diff --git a/webapp/channels/src/components/sidebar/__snapshots__/sidebar.test.tsx.snap b/webapp/channels/src/components/sidebar/__snapshots__/sidebar.test.tsx.snap index 9de1316ce0..2a79c0e72c 100644 --- a/webapp/channels/src/components/sidebar/__snapshots__/sidebar.test.tsx.snap +++ b/webapp/channels/src/components/sidebar/__snapshots__/sidebar.test.tsx.snap @@ -85,7 +85,7 @@ exports[`components/sidebar should match snapshot when direct channels modal is onDragStart={[Function]} /> - diff --git a/webapp/channels/src/components/sidebar/sidebar.test.tsx b/webapp/channels/src/components/sidebar/sidebar.test.tsx index 51aa8deaf3..69692ff742 100644 --- a/webapp/channels/src/components/sidebar/sidebar.test.tsx +++ b/webapp/channels/src/components/sidebar/sidebar.test.tsx @@ -9,7 +9,7 @@ import type {DeepPartial} from '@mattermost/types/utilities'; import {Preferences} from 'mattermost-redux/constants'; import mergeObjects from 'packages/mattermost-redux/test/merge_objects'; -import {renderWithContext, screen} from 'tests/react_testing_utils'; +import {renderWithContext, screen, waitFor} from 'tests/react_testing_utils'; import Constants, {ModalIdentifiers} from 'utils/constants'; import {TestHelper} from 'utils/test_helper'; @@ -164,7 +164,7 @@ describe('components/sidebar', () => { }, }; - test('should not render unreads category when disabled by user preference', () => { + test('should not render unreads category when disabled by user preference', async () => { const testState = { entities: { channels: { @@ -185,10 +185,12 @@ describe('components/sidebar', () => { mergeObjects(baseState, testState), ); - expect(screen.queryByText('UNREADS')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('UNREADS')).not.toBeInTheDocument(); + }); }); - test('should render unreads category when there are unread channels', () => { + test('should render unreads category when there are unread channels', async () => { const testState: DeepPartial = { entities: { channels: { @@ -209,10 +211,12 @@ describe('components/sidebar', () => { mergeObjects(baseState, testState), ); - expect(screen.queryByText('UNREADS')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('UNREADS')).toBeInTheDocument(); + }); }); - test('should not render unreads category when there are no unread channels', () => { + test('should not render unreads category when there are no unread channels', async () => { const testState: DeepPartial = { entities: { preferences: { @@ -228,10 +232,12 @@ describe('components/sidebar', () => { mergeObjects(baseState, testState), ); - expect(screen.queryByText('UNREADS')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('UNREADS')).not.toBeInTheDocument(); + }); }); - test('should render unreads category when there are no unread channels but the current channel was previously unread', () => { + test('should render unreads category when there are no unread channels but the current channel was previously unread', async () => { const testState: DeepPartial = { entities: { preferences: { @@ -252,7 +258,9 @@ describe('components/sidebar', () => { mergeObjects(baseState, testState), ); - expect(screen.queryByText('UNREADS')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.queryByText('UNREADS')).toBeInTheDocument(); + }); }); }); }); diff --git a/webapp/channels/src/components/sidebar/sidebar.tsx b/webapp/channels/src/components/sidebar/sidebar.tsx index 0400d0836e..ad62382fa5 100644 --- a/webapp/channels/src/components/sidebar/sidebar.tsx +++ b/webapp/channels/src/components/sidebar/sidebar.tsx @@ -2,34 +2,36 @@ // See LICENSE.txt for license information. import classNames from 'classnames'; -import React from 'react'; +import React, {lazy} from 'react'; import {trackEvent} from 'actions/telemetry_actions'; -import BrowseChannels from 'components/browse_channels'; -import CreateUserGroupsModal from 'components/create_user_groups_modal'; +import {makeAsyncComponent} from 'components/async_load'; import DataPrefetch from 'components/data_prefetch'; -import EditCategoryModal from 'components/edit_category_modal'; -import InvitationModal from 'components/invitation_modal'; -import KeyboardShortcutsModal from 'components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal'; -import MoreDirectChannels from 'components/more_direct_channels'; -import NewChannelModal from 'components/new_channel_modal/new_channel_modal'; import ResizableLhs from 'components/resizable_sidebar/resizable_lhs'; -import UserSettingsModal from 'components/user_settings/modal'; +import SidebarHeader from 'components/sidebar/sidebar_header'; import Pluggable from 'plugins/pluggable'; import Constants, {ModalIdentifiers, RHSStates} from 'utils/constants'; -import * as Keyboard from 'utils/keyboard'; -import * as Utils from 'utils/utils'; +import {isKeyPressed, cmdOrCtrlPressed} from 'utils/keyboard'; +import {localizeMessage} from 'utils/utils'; import type {ModalData} from 'types/actions'; import type {RhsState} from 'types/store/rhs'; import ChannelNavigator from './channel_navigator'; -import MobileSidebarHeader from './mobile_sidebar_header'; -import SidebarHeader from './sidebar_header'; import SidebarList from './sidebar_list'; +const MobileSidebarHeader = makeAsyncComponent('MobileSidebarHeader', lazy(() => import('./mobile_sidebar_header'))); +const MoreDirectChannels = makeAsyncComponent('MoreDirectChannels', lazy(() => import('components/more_direct_channels'))); +const BrowseChannels = makeAsyncComponent('BrowseChannels', lazy(() => import('components/browse_channels'))); +const EditCategoryModal = makeAsyncComponent('EditCategoryModal', lazy(() => import('components/edit_category_modal'))); +const CreateUserGroupsModal = makeAsyncComponent('CreateUserGroupsModal', lazy(() => import('components/create_user_groups_modal'))); +const InvitationModal = makeAsyncComponent('InvitationModal', lazy(() => import('components/invitation_modal'))); +const KeyboardShortcutsModal = makeAsyncComponent('KeyboardShortcutsModal', lazy(() => import('components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal'))); +const NewChannelModal = makeAsyncComponent('NewChannelModal', lazy(() => import('components/new_channel_modal/new_channel_modal'))); +const UserSettingsModal = makeAsyncComponent('UserSettingsModal', lazy(() => import('components/user_settings/modal'))); + type Props = { teamId: string; canCreatePublicChannel: boolean; @@ -95,15 +97,15 @@ export default class Sidebar extends React.PureComponent { }; handleKeyDownEvent = (event: KeyboardEvent) => { - if (Keyboard.isKeyPressed(event, Constants.KeyCodes.ESCAPE)) { + if (isKeyPressed(event, Constants.KeyCodes.ESCAPE)) { this.props.actions.clearChannelSelection(); return; } - const ctrlOrMetaKeyPressed = Keyboard.cmdOrCtrlPressed(event, true); + const ctrlOrMetaKeyPressed = cmdOrCtrlPressed(event, true); if (ctrlOrMetaKeyPressed) { - if (Keyboard.isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) { + if (isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) { event.preventDefault(); if (this.props.isKeyBoardShortcutModalOpen) { this.props.actions.closeModal(ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL); @@ -113,7 +115,7 @@ export default class Sidebar extends React.PureComponent { dialogType: KeyboardShortcutsModal, }); } - } else if (Keyboard.isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) { + } else if (isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) { event.preventDefault(); this.props.actions.openModal({ @@ -225,7 +227,7 @@ export default class Sidebar extends React.PureComponent { return (
); } - const ariaLabel = Utils.localizeMessage('accessibility.sections.lhsNavigator', 'channel navigator region'); + const ariaLabel = localizeMessage('accessibility.sections.lhsNavigator', 'channel navigator region'); return ( - +
import('components/drafts/drafts_link/drafts_link'))); +const GlobalThreadsLink = makeAsyncComponent('GlobalThreadsLink', lazy(() => import('components/threading/global_threads_link'))); +const UnreadChannelIndicator = makeAsyncComponent('UnreadChannelIndicator', lazy(() => import('../unread_channel_indicator'))); +const UnreadChannels = makeAsyncComponent('UnreadChannels', lazy(() => import('../unread_channels'))); export function renderView(props: React.HTMLProps) { return ( @@ -318,7 +319,7 @@ export default class SidebarList extends React.PureComponent { }; navigateChannelShortcut = (e: KeyboardEvent) => { - if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP) || Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN))) { + if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && (isKeyPressed(e, Constants.KeyCodes.UP) || isKeyPressed(e, Constants.KeyCodes.DOWN))) { e.preventDefault(); const staticPageIds = this.getDisplayedStaticPageIds(); @@ -328,24 +329,24 @@ export default class SidebarList extends React.PureComponent { const curIndex = allIds.indexOf(curSelectedId); let nextIndex; - if (Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN)) { + if (isKeyPressed(e, Constants.KeyCodes.DOWN)) { nextIndex = curIndex + 1; } else { nextIndex = curIndex - 1; } - const nextId = allIds[Utils.mod(nextIndex, allIds.length)]; + const nextId = allIds[mod(nextIndex, allIds.length)]; this.navigateById(nextId); if (nextIndex >= staticPageIds.length) { this.scrollToChannel(nextId); } - } else if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.K)) { + } else if (cmdOrCtrlPressed(e) && e.shiftKey && isKeyPressed(e, Constants.KeyCodes.K)) { this.props.handleOpenMoreDirectChannelsModal(e); } }; navigateUnreadChannelShortcut = (e: KeyboardEvent) => { - if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP) || Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN))) { + if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && (isKeyPressed(e, Constants.KeyCodes.UP) || isKeyPressed(e, Constants.KeyCodes.DOWN))) { e.preventDefault(); const allChannelIds = this.getDisplayedChannelIds(); @@ -360,13 +361,13 @@ export default class SidebarList extends React.PureComponent { } let direction = 0; - if (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP)) { + if (isKeyPressed(e, Constants.KeyCodes.UP)) { direction = -1; } else { direction = 1; } - const nextIndex = ChannelUtils.findNextUnreadChannelId( + const nextIndex = findNextUnreadChannelId( this.props.currentChannelId, allChannelIds, unreadChannelIds, @@ -546,7 +547,7 @@ export default class SidebarList extends React.PureComponent { /> ); - const ariaLabel = Utils.localizeMessage('accessibility.sections.lhsList', 'channel sidebar region'); + const ariaLabel = localizeMessage('accessibility.sections.lhsList', 'channel sidebar region'); return ( diff --git a/webapp/channels/src/components/sidebar_right_menu/constant.ts b/webapp/channels/src/components/sidebar_mobile_right_menu/constant.ts similarity index 100% rename from webapp/channels/src/components/sidebar_right_menu/constant.ts rename to webapp/channels/src/components/sidebar_mobile_right_menu/constant.ts diff --git a/webapp/channels/src/components/sidebar_right_menu/index.ts b/webapp/channels/src/components/sidebar_mobile_right_menu/index.ts similarity index 86% rename from webapp/channels/src/components/sidebar_right_menu/index.ts rename to webapp/channels/src/components/sidebar_mobile_right_menu/index.ts index bfc8329ce8..aa938152f8 100644 --- a/webapp/channels/src/components/sidebar_right_menu/index.ts +++ b/webapp/channels/src/components/sidebar_mobile_right_menu/index.ts @@ -11,7 +11,7 @@ import {getIsMobileView} from 'selectors/views/browser'; import type {GlobalState} from 'types/store'; -import SidebarRightMenu from './sidebar_right_menu'; +import SidebarMobileRightMenu from './sidebar_mobile_right_menu'; function mapStateToProps(state: GlobalState) { const config = getConfig(state); @@ -27,4 +27,4 @@ function mapStateToProps(state: GlobalState) { }; } -export default connect(mapStateToProps)(SidebarRightMenu); +export default connect(mapStateToProps)(SidebarMobileRightMenu); diff --git a/webapp/channels/src/components/sidebar_right_menu/sidebar_right_menu.tsx b/webapp/channels/src/components/sidebar_mobile_right_menu/sidebar_mobile_right_menu.tsx similarity index 100% rename from webapp/channels/src/components/sidebar_right_menu/sidebar_right_menu.tsx rename to webapp/channels/src/components/sidebar_mobile_right_menu/sidebar_mobile_right_menu.tsx diff --git a/webapp/channels/src/utils/performance_telemetry/element_identification.test.tsx b/webapp/channels/src/utils/performance_telemetry/element_identification.test.tsx index 9407d7cdf9..542dc973df 100644 --- a/webapp/channels/src/utils/performance_telemetry/element_identification.test.tsx +++ b/webapp/channels/src/utils/performance_telemetry/element_identification.test.tsx @@ -131,7 +131,9 @@ describe('identifyElementRegion', () => { }, ); - expect(identifyElementRegion(screen.getAllByText(channel.display_name)[0])).toEqual('channel_sidebar'); + await waitFor(() => { + expect(identifyElementRegion(screen.getAllByText(channel.display_name)[0])).toEqual('channel_sidebar'); + }); expect(identifyElementRegion(screen.getAllByText(channel.display_name)[1])).toEqual('channel_header'); expect(identifyElementRegion(screen.getAllByText(channel.header)[0])).toEqual('channel_header');