mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Fix several re-renders on init (#26361)
* Fix several re-renders on init * Fix tests * Address feedback --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
19d59d1126
commit
e8b4892877
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useState, useEffect, useMemo} from 'react';
|
||||
import {useEffect, useMemo, useRef} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import type {Product} from '@mattermost/types/cloud';
|
||||
@ -19,14 +19,15 @@ export default function useGetSelfHostedProducts(): [Record<string, Product>, bo
|
||||
const products = useSelector(getSelfHostedProducts);
|
||||
const productsReceived = useSelector(getSelfHostedProductsLoaded);
|
||||
const dispatch = useDispatch();
|
||||
const [requested, setRequested] = useState(false);
|
||||
const requested = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && !isCloud && !requested && !productsReceived) {
|
||||
if (isLoggedIn && !isCloud && !requested.current && !productsReceived) {
|
||||
dispatch(getSelfHostedProductsAction());
|
||||
setRequested(true);
|
||||
requested.current = true;
|
||||
}
|
||||
}, [isLoggedIn, isCloud, requested, productsReceived]);
|
||||
}, [isLoggedIn, isCloud, productsReceived]);
|
||||
|
||||
const result: [Record<string, Product>, boolean] = useMemo(() => {
|
||||
return [products, productsReceived];
|
||||
}, [products, productsReceived]);
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React, {useMemo} from 'react';
|
||||
|
||||
import ThemeProvider, {lightTheme} from '@mattermost/compass-components/utilities/theme'; // eslint-disable-line no-restricted-imports
|
||||
|
||||
@ -12,45 +12,48 @@ type Props = {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const CompassThemeProvider = ({theme, children}: Props): JSX.Element | null => {
|
||||
const [compassTheme, setCompassTheme] = useState({
|
||||
...lightTheme,
|
||||
noStyleReset: true,
|
||||
noDefaultStyle: true,
|
||||
noFontFaces: true,
|
||||
});
|
||||
const CompassThemeProvider = ({
|
||||
theme,
|
||||
children,
|
||||
}: Props) => {
|
||||
const compassTheme = useMemo(() => {
|
||||
const base = {
|
||||
...lightTheme,
|
||||
noStyleReset: true,
|
||||
noDefaultStyle: true,
|
||||
noFontFaces: true,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setCompassTheme({
|
||||
...compassTheme,
|
||||
return {
|
||||
...base,
|
||||
palette: {
|
||||
...compassTheme.palette,
|
||||
...base.palette,
|
||||
primary: {
|
||||
...compassTheme.palette.primary,
|
||||
...base.palette.primary,
|
||||
main: theme.sidebarHeaderBg,
|
||||
contrast: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
alert: {
|
||||
...compassTheme.palette.alert,
|
||||
...base.palette.alert,
|
||||
main: theme.dndIndicator,
|
||||
},
|
||||
},
|
||||
action: {
|
||||
...compassTheme.action,
|
||||
...base.action,
|
||||
hover: theme.sidebarHeaderTextColor,
|
||||
disabled: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
badges: {
|
||||
...compassTheme.badges,
|
||||
...base.badges,
|
||||
online: theme.onlineIndicator,
|
||||
away: theme.awayIndicator,
|
||||
dnd: theme.dndIndicator,
|
||||
},
|
||||
text: {
|
||||
...compassTheme.text,
|
||||
...base.text,
|
||||
primary: theme.sidebarHeaderTextColor,
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
|
@ -161,11 +161,11 @@ const Skeleton = styled.div`
|
||||
`;
|
||||
|
||||
const OnBoardingTaskList = (): JSX.Element | null => {
|
||||
const myPreferences = useSelector((state: GlobalState) => getMyPreferencesSelector(state));
|
||||
const hasPreferences = useSelector((state: GlobalState) => Object.keys(getMyPreferencesSelector(state)).length !== 0);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getPrevTrialLicense());
|
||||
if (Object.keys(myPreferences).length === 0) {
|
||||
if (!hasPreferences) {
|
||||
dispatch(getMyPreferences());
|
||||
}
|
||||
}, []);
|
||||
@ -182,7 +182,10 @@ const OnBoardingTaskList = (): JSX.Element | null => {
|
||||
const isCurrentUserSystemAdmin = useIsCurrentUserSystemAdmin();
|
||||
const isFirstAdmin = useFirstAdminUser();
|
||||
const isEnableOnboardingFlow = useSelector((state: GlobalState) => getConfig(state).EnableOnboardingFlow === 'true');
|
||||
const [showTaskList, firstTimeOnboarding] = useSelector(getShowTaskListBool);
|
||||
const [showTaskList, firstTimeOnboarding] = useSelector(
|
||||
getShowTaskListBool,
|
||||
(a, b) => a[0] === b[0] && a[1] === b[1],
|
||||
);
|
||||
const theme = useSelector(getTheme);
|
||||
|
||||
const startTask = (taskName: string) => {
|
||||
@ -284,7 +287,7 @@ const OnBoardingTaskList = (): JSX.Element | null => {
|
||||
}));
|
||||
}, []);
|
||||
|
||||
if (Object.keys(myPreferences).length === 0 || !showTaskList || !isEnableOnboardingFlow) {
|
||||
if (!hasPreferences || !showTaskList || !isEnableOnboardingFlow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {memo, useCallback} from 'react';
|
||||
import React, {memo, useCallback, useMemo} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
@ -42,13 +42,12 @@ type Props = {
|
||||
category: ChannelCategory;
|
||||
};
|
||||
|
||||
const getUnreadsIdsForCategory = makeGetUnreadIdsForCategory();
|
||||
|
||||
const SidebarCategoryMenu = ({
|
||||
category,
|
||||
}: Props) => {
|
||||
const dispatch = useDispatch();
|
||||
const showUnreadsCategory = useSelector(shouldShowUnreadsCategory);
|
||||
const getUnreadsIdsForCategory = useMemo(makeGetUnreadIdsForCategory, [category]);
|
||||
const unreadsIds = useSelector((state: GlobalState) => getUnreadsIdsForCategory(state, category));
|
||||
|
||||
const {formatMessage} = useIntl();
|
||||
|
@ -8,7 +8,6 @@ import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {favoriteChannel, unfavoriteChannel, markMultipleChannelsAsRead} from 'mattermost-redux/actions/channels';
|
||||
import Permissions from 'mattermost-redux/constants/permissions';
|
||||
import {getCategoryInTeamWithChannel} from 'mattermost-redux/selectors/entities/channel_categories';
|
||||
import {isFavoriteChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getMyChannelMemberships, getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
|
||||
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||
@ -17,9 +16,7 @@ import {isChannelMuted} from 'mattermost-redux/utils/channel_utils';
|
||||
|
||||
import {unmuteChannel, muteChannel} from 'actions/channel_actions';
|
||||
import {markMostRecentPostInChannelAsUnread} from 'actions/post_actions';
|
||||
import {addChannelsInSidebar} from 'actions/views/channel_sidebar';
|
||||
import {openModal} from 'actions/views/modals';
|
||||
import {getCategoriesForCurrentTeam, getDisplayedChannels} from 'selectors/views/channel_sidebar';
|
||||
|
||||
import {getSiteURL} from 'utils/url';
|
||||
|
||||
@ -41,27 +38,19 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
|
||||
let managePublicChannelMembers = false;
|
||||
let managePrivateChannelMembers = false;
|
||||
let categories;
|
||||
let currentCategory;
|
||||
|
||||
if (currentTeam) {
|
||||
managePublicChannelMembers = haveIChannelPermission(state, currentTeam.id, ownProps.channel.id, Permissions.MANAGE_PUBLIC_CHANNEL_MEMBERS);
|
||||
managePrivateChannelMembers = haveIChannelPermission(state, currentTeam.id, ownProps.channel.id, Permissions.MANAGE_PRIVATE_CHANNEL_MEMBERS);
|
||||
categories = getCategoriesForCurrentTeam(state);
|
||||
currentCategory = getCategoryInTeamWithChannel(state, currentTeam.id, ownProps.channel.id);
|
||||
}
|
||||
|
||||
return {
|
||||
currentUserId: getCurrentUserId(state),
|
||||
categories,
|
||||
currentCategory,
|
||||
isFavorite: isFavoriteChannel(state, ownProps.channel.id),
|
||||
isMuted: isChannelMuted(member),
|
||||
channelLink: `${getSiteURL()}${ownProps.channelLink}`,
|
||||
managePublicChannelMembers,
|
||||
managePrivateChannelMembers,
|
||||
displayedChannels: getDisplayedChannels(state),
|
||||
multiSelectedChannelIds: state.views.channelSidebar.multiSelectedChannelIds,
|
||||
};
|
||||
}
|
||||
|
||||
@ -73,7 +62,6 @@ const mapDispatchToProps = {
|
||||
muteChannel,
|
||||
unmuteChannel,
|
||||
openModal,
|
||||
addChannelsInSidebar,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
@ -29,22 +29,40 @@ import type {PropsFromRedux, OwnProps} from './index';
|
||||
|
||||
type Props = PropsFromRedux & OwnProps;
|
||||
|
||||
const SidebarChannelMenu = (props: Props) => {
|
||||
const SidebarChannelMenu = ({
|
||||
channel,
|
||||
channelLink,
|
||||
currentUserId,
|
||||
favoriteChannel,
|
||||
isFavorite,
|
||||
isMuted,
|
||||
isUnread,
|
||||
managePrivateChannelMembers,
|
||||
managePublicChannelMembers,
|
||||
markMultipleChannelsAsRead,
|
||||
markMostRecentPostInChannelAsUnread,
|
||||
muteChannel,
|
||||
onMenuToggle,
|
||||
openModal,
|
||||
unfavoriteChannel,
|
||||
unmuteChannel,
|
||||
channelLeaveHandler,
|
||||
}: Props) => {
|
||||
const isLeaving = useRef(false);
|
||||
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
let markAsReadUnreadMenuItem: JSX.Element | null = null;
|
||||
if (props.isUnread) {
|
||||
if (isUnread) {
|
||||
function handleMarkAsRead() {
|
||||
// We use mark multiple to not update the active channel in the server
|
||||
props.markMultipleChannelsAsRead({[props.channel.id]: Date.now()});
|
||||
markMultipleChannelsAsRead({[channel.id]: Date.now()});
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_markAsRead');
|
||||
}
|
||||
|
||||
markAsReadUnreadMenuItem = (
|
||||
<Menu.Item
|
||||
id={`markAsRead-${props.channel.id}`}
|
||||
id={`markAsRead-${channel.id}`}
|
||||
onClick={handleMarkAsRead}
|
||||
leadingElement={<MarkAsUnreadIcon size={18}/>}
|
||||
labels={(
|
||||
@ -58,13 +76,13 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
);
|
||||
} else {
|
||||
function handleMarkAsUnread() {
|
||||
props.markMostRecentPostInChannelAsUnread(props.channel.id);
|
||||
markMostRecentPostInChannelAsUnread(channel.id);
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_markAsUnread');
|
||||
}
|
||||
|
||||
markAsReadUnreadMenuItem = (
|
||||
<Menu.Item
|
||||
id={`markAsUnread-${props.channel.id}`}
|
||||
id={`markAsUnread-${channel.id}`}
|
||||
onClick={handleMarkAsUnread}
|
||||
leadingElement={<MarkAsUnreadIcon size={18}/>}
|
||||
labels={(
|
||||
@ -78,15 +96,15 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
let favoriteUnfavoriteMenuItem: JSX.Element | null = null;
|
||||
if (props.isFavorite) {
|
||||
if (isFavorite) {
|
||||
function handleUnfavoriteChannel() {
|
||||
props.unfavoriteChannel(props.channel.id);
|
||||
unfavoriteChannel(channel.id);
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_unfavorite');
|
||||
}
|
||||
|
||||
favoriteUnfavoriteMenuItem = (
|
||||
<Menu.Item
|
||||
id={`unfavorite-${props.channel.id}`}
|
||||
id={`unfavorite-${channel.id}`}
|
||||
onClick={handleUnfavoriteChannel}
|
||||
leadingElement={<StarIcon size={18}/>}
|
||||
labels={(
|
||||
@ -99,14 +117,14 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
);
|
||||
} else {
|
||||
function handleFavoriteChannel() {
|
||||
props.favoriteChannel(props.channel.id);
|
||||
favoriteChannel(channel.id);
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_favorite');
|
||||
}
|
||||
|
||||
favoriteUnfavoriteMenuItem = (
|
||||
|
||||
<Menu.Item
|
||||
id={`favorite-${props.channel.id}`}
|
||||
id={`favorite-${channel.id}`}
|
||||
onClick={handleFavoriteChannel}
|
||||
leadingElement={<StarOutlineIcon size={18}/>}
|
||||
labels={(
|
||||
@ -120,14 +138,14 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
let muteUnmuteChannelMenuItem: JSX.Element | null = null;
|
||||
if (props.isMuted) {
|
||||
if (isMuted) {
|
||||
let muteChannelText = (
|
||||
<FormattedMessage
|
||||
id='sidebar_left.sidebar_channel_menu.unmuteChannel'
|
||||
defaultMessage='Unmute Channel'
|
||||
/>
|
||||
);
|
||||
if (props.channel.type === Constants.DM_CHANNEL || props.channel.type === Constants.GM_CHANNEL) {
|
||||
if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
|
||||
muteChannelText = (
|
||||
<FormattedMessage
|
||||
id='sidebar_left.sidebar_channel_menu.unmuteConversation'
|
||||
@ -137,12 +155,12 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
function handleUnmuteChannel() {
|
||||
props.unmuteChannel(props.currentUserId, props.channel.id);
|
||||
unmuteChannel(currentUserId, channel.id);
|
||||
}
|
||||
|
||||
muteUnmuteChannelMenuItem = (
|
||||
<Menu.Item
|
||||
id={`unmute-${props.channel.id}`}
|
||||
id={`unmute-${channel.id}`}
|
||||
onClick={handleUnmuteChannel}
|
||||
leadingElement={<BellOffOutlineIcon size={18}/>}
|
||||
labels={muteChannelText}
|
||||
@ -155,7 +173,7 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
defaultMessage='Mute Channel'
|
||||
/>
|
||||
);
|
||||
if (props.channel.type === Constants.DM_CHANNEL || props.channel.type === Constants.GM_CHANNEL) {
|
||||
if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
|
||||
muteChannelText = (
|
||||
<FormattedMessage
|
||||
id='sidebar_left.sidebar_channel_menu.muteConversation'
|
||||
@ -165,12 +183,12 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
function handleMuteChannel() {
|
||||
props.muteChannel(props.currentUserId, props.channel.id);
|
||||
muteChannel(currentUserId, channel.id);
|
||||
}
|
||||
|
||||
muteUnmuteChannelMenuItem = (
|
||||
<Menu.Item
|
||||
id={`mute-${props.channel.id}`}
|
||||
id={`mute-${channel.id}`}
|
||||
onClick={handleMuteChannel}
|
||||
leadingElement={<BellOutlineIcon size={18}/>}
|
||||
labels={muteChannelText}
|
||||
@ -179,14 +197,14 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
let copyLinkMenuItem: JSX.Element | null = null;
|
||||
if (props.channel.type === Constants.OPEN_CHANNEL || props.channel.type === Constants.PRIVATE_CHANNEL) {
|
||||
if (channel.type === Constants.OPEN_CHANNEL || channel.type === Constants.PRIVATE_CHANNEL) {
|
||||
function handleCopyLink() {
|
||||
copyToClipboard(props.channelLink);
|
||||
copyToClipboard(channelLink);
|
||||
}
|
||||
|
||||
copyLinkMenuItem = (
|
||||
<Menu.Item
|
||||
id={`copyLink-${props.channel.id}`}
|
||||
id={`copyLink-${channel.id}`}
|
||||
onClick={handleCopyLink}
|
||||
leadingElement={<LinkVariantIcon size={18}/>}
|
||||
labels={(
|
||||
@ -200,19 +218,19 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
let addMembersMenuItem: JSX.Element | null = null;
|
||||
if ((props.channel.type === Constants.PRIVATE_CHANNEL && props.managePrivateChannelMembers) || (props.channel.type === Constants.OPEN_CHANNEL && props.managePublicChannelMembers)) {
|
||||
if ((channel.type === Constants.PRIVATE_CHANNEL && managePrivateChannelMembers) || (channel.type === Constants.OPEN_CHANNEL && managePublicChannelMembers)) {
|
||||
function handleAddMembers() {
|
||||
props.openModal({
|
||||
openModal({
|
||||
modalId: ModalIdentifiers.CHANNEL_INVITE,
|
||||
dialogType: ChannelInviteModal,
|
||||
dialogProps: {channel: props.channel},
|
||||
dialogProps: {channel},
|
||||
});
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_addMembers');
|
||||
}
|
||||
|
||||
addMembersMenuItem = (
|
||||
<Menu.Item
|
||||
id={`addMembers-${props.channel.id}`}
|
||||
id={`addMembers-${channel.id}`}
|
||||
onClick={handleAddMembers}
|
||||
aria-haspopup='true'
|
||||
leadingElement={<AccountPlusOutlineIcon size={18}/>}
|
||||
@ -227,14 +245,14 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
let leaveChannelMenuItem: JSX.Element | null = null;
|
||||
if (props.channel.name !== Constants.DEFAULT_CHANNEL) {
|
||||
if (channel.name !== Constants.DEFAULT_CHANNEL) {
|
||||
let leaveChannelText = (
|
||||
<FormattedMessage
|
||||
id='sidebar_left.sidebar_channel_menu.leaveChannel'
|
||||
defaultMessage='Leave Channel'
|
||||
/>
|
||||
);
|
||||
if (props.channel.type === Constants.DM_CHANNEL || props.channel.type === Constants.GM_CHANNEL) {
|
||||
if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
|
||||
leaveChannelText = (
|
||||
<FormattedMessage
|
||||
id='sidebar_left.sidebar_channel_menu.leaveConversation'
|
||||
@ -244,13 +262,13 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
}
|
||||
|
||||
function handleLeaveChannel() {
|
||||
if (isLeaving.current || !props.channelLeaveHandler) {
|
||||
if (isLeaving.current || !channelLeaveHandler) {
|
||||
return;
|
||||
}
|
||||
|
||||
isLeaving.current = true;
|
||||
|
||||
props.channelLeaveHandler(() => {
|
||||
channelLeaveHandler(() => {
|
||||
isLeaving.current = false;
|
||||
});
|
||||
trackEvent('ui', 'ui_sidebar_channel_menu_leave');
|
||||
@ -258,7 +276,7 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
|
||||
leaveChannelMenuItem = (
|
||||
<Menu.Item
|
||||
id={`leave-${props.channel.id}`}
|
||||
id={`leave-${channel.id}`}
|
||||
onClick={handleLeaveChannel}
|
||||
leadingElement={<ExitToAppIcon size={18}/>}
|
||||
labels={leaveChannelText}
|
||||
@ -270,30 +288,30 @@ const SidebarChannelMenu = (props: Props) => {
|
||||
return (
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
id: `SidebarChannelMenu-Button-${props.channel.id}`,
|
||||
id: `SidebarChannelMenu-Button-${channel.id}`,
|
||||
class: 'SidebarMenu_menuButton',
|
||||
'aria-label': formatMessage({
|
||||
id: 'sidebar_left.sidebar_channel_menu.editChannel.ariaLabel',
|
||||
defaultMessage: 'Channel options for {channelName}',
|
||||
}, {channelName: props.channel.name}),
|
||||
}, {channelName: channel.name}),
|
||||
children: <DotsVerticalIcon size={16}/>,
|
||||
}}
|
||||
menuButtonTooltip={{
|
||||
id: `SidebarChannelMenu-ButtonTooltip-${props.channel.id}`,
|
||||
id: `SidebarChannelMenu-ButtonTooltip-${channel.id}`,
|
||||
class: 'hidden-xs',
|
||||
text: formatMessage({id: 'sidebar_left.sidebar_channel_menu.editChannel', defaultMessage: 'Channel options'}),
|
||||
}}
|
||||
menu={{
|
||||
id: `SidebarChannelMenu-MenuList-${props.channel.id}`,
|
||||
id: `SidebarChannelMenu-MenuList-${channel.id}`,
|
||||
'aria-label': formatMessage({id: 'sidebar_left.sidebar_channel_menu.dropdownAriaLabel', defaultMessage: 'Edit channel menu'}),
|
||||
onToggle: props.onMenuToggle,
|
||||
onToggle: onMenuToggle,
|
||||
}}
|
||||
>
|
||||
{markAsReadUnreadMenuItem}
|
||||
{favoriteUnfavoriteMenuItem}
|
||||
{muteUnmuteChannelMenuItem}
|
||||
<Menu.Separator/>
|
||||
<ChannelMoveToSubmenu channel={props.channel}/>
|
||||
<ChannelMoveToSubmenu channel={channel}/>
|
||||
{(copyLinkMenuItem || addMembersMenuItem) && <Menu.Separator/>}
|
||||
{copyLinkMenuItem}
|
||||
{addMembersMenuItem}
|
||||
|
@ -35,7 +35,7 @@ import SidebarCategory from '../sidebar_category';
|
||||
import UnreadChannelIndicator from '../unread_channel_indicator';
|
||||
import UnreadChannels from '../unread_channels';
|
||||
|
||||
export function renderView(props: any) {
|
||||
export function renderView(props: React.HTMLProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@ -44,7 +44,7 @@ export function renderView(props: any) {
|
||||
);
|
||||
}
|
||||
|
||||
export function renderThumbHorizontal(props: any) {
|
||||
export function renderThumbHorizontal(props: React.HTMLProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@ -53,7 +53,7 @@ export function renderThumbHorizontal(props: any) {
|
||||
);
|
||||
}
|
||||
|
||||
export function renderTrackVertical(props: any) {
|
||||
export function renderTrackVertical(props: React.HTMLProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@ -62,7 +62,7 @@ export function renderTrackVertical(props: any) {
|
||||
);
|
||||
}
|
||||
|
||||
export function renderThumbVertical(props: any) {
|
||||
export function renderThumbVertical(props: React.HTMLProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
|
@ -61,10 +61,9 @@ const GlobalThreadsLink = () => {
|
||||
const crtTutorialTrigger = useSelector((state: GlobalState) => getInt(state, Preferences.CRT_TUTORIAL_TRIGGERED, currentUserId, Constants.CrtTutorialTriggerSteps.START));
|
||||
const threads = useSelector(getThreadsInCurrentTeam);
|
||||
const showTutorialTip = crtTutorialTrigger === CrtTutorialTriggerSteps.STARTED && tipStep === CrtTutorialSteps.WELCOME_POPOVER && threads.length >= 1;
|
||||
const threadsCount = useSelector(getThreadCountsInCurrentTeam);
|
||||
const rhsOpen = useSelector(getIsRhsOpen);
|
||||
const rhsState = useSelector(getRhsState);
|
||||
const showTutorialTrigger = isFeatureEnabled && crtTutorialTrigger === Constants.CrtTutorialTriggerSteps.START && !appHaveOpenModal && Boolean(threadsCount) && threadsCount.total >= 1;
|
||||
const showTutorialTrigger = isFeatureEnabled && crtTutorialTrigger === Constants.CrtTutorialTriggerSteps.START && !appHaveOpenModal && Boolean(counts) && counts.total >= 1;
|
||||
const openThreads = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
@ -79,7 +78,7 @@ const GlobalThreadsLink = () => {
|
||||
if (rhsOpen && rhsState === RHSStates.EDIT_HISTORY) {
|
||||
dispatch(closeRightHandSide());
|
||||
}
|
||||
}, [showTutorialTrigger, threadsCount, threads, rhsOpen, rhsState]);
|
||||
}, [showTutorialTrigger, counts, threads, rhsOpen, rhsState]);
|
||||
|
||||
useEffect(() => {
|
||||
// load counts if necessary
|
||||
|
@ -1386,35 +1386,6 @@ describe('makeGetChannelsByCategory', () => {
|
||||
expect(result).toBe(previousResult);
|
||||
});
|
||||
|
||||
test('should return a new object when user profiles change', () => {
|
||||
// This behaviour isn't ideal, but it's better than the previous version which returns a new object
|
||||
// whenever anything user-related changes
|
||||
const getChannelsByCategory = Selectors.makeGetChannelsByCategory();
|
||||
|
||||
const state = mergeObjects(baseState, {
|
||||
entities: {
|
||||
users: {
|
||||
profiles: {
|
||||
newUser: {id: 'newUser'},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const previousResult = getChannelsByCategory(baseState, 'team1');
|
||||
const result = getChannelsByCategory(state, 'team1');
|
||||
|
||||
expect(result).not.toBe(previousResult);
|
||||
expect(result).toEqual(previousResult);
|
||||
|
||||
// Categories not containing DMs/GMs and sorted alphabetically should still remain the same
|
||||
expect(result.favoritesCategory).not.toBe(previousResult.favoritesCategory);
|
||||
expect(result.favoritesCategory).toEqual(previousResult.favoritesCategory);
|
||||
expect(result.channelsCategory).toBe(previousResult.channelsCategory);
|
||||
expect(result.directMessagesCategory).toEqual(previousResult.directMessagesCategory);
|
||||
expect(result.directMessagesCategory).toEqual(previousResult.directMessagesCategory);
|
||||
});
|
||||
|
||||
test('should return the same object when other user state changes', () => {
|
||||
const getChannelsByCategory = Selectors.makeGetChannelsByCategory();
|
||||
|
||||
|
@ -447,13 +447,18 @@ export function makeGetChannelsByCategory() {
|
||||
|
||||
const channelsByCategory: RelationOneToOne<ChannelCategory, Channel[]> = {};
|
||||
|
||||
// TODO: This avoids some rendering, but there is a bigger issue underneath
|
||||
// Every time myPreferences or myChannels change (which can happen for many
|
||||
// unrelated reasons) the whole list of channels gets reordered and re-filtered.
|
||||
let allEquals = categoryIds === lastCategoryIds;
|
||||
for (const category of categories) {
|
||||
const channels = getChannels[category.id](state, category.channel_ids);
|
||||
channelsByCategory[category.id] = filterAndSortChannels[category.id](state, channels, category);
|
||||
allEquals = allEquals && shallowEquals(channelsByCategory[category.id], lastChannelsByCategory[category.id]);
|
||||
}
|
||||
|
||||
// Do a shallow equality check of channelsByCategory to avoid returning a new object containing the same data
|
||||
if (shallowEquals(channelsByCategory, lastChannelsByCategory)) {
|
||||
if (allEquals) {
|
||||
return lastChannelsByCategory;
|
||||
}
|
||||
|
||||
|
@ -1259,7 +1259,10 @@ export function getChannelModerations(state: GlobalState, channelId: string): Ch
|
||||
}
|
||||
|
||||
const EMPTY_OBJECT = {};
|
||||
export function getChannelMemberCountsByGroup(state: GlobalState, channelId: string): ChannelMemberCountsByGroup {
|
||||
export function getChannelMemberCountsByGroup(state: GlobalState, channelId?: string): ChannelMemberCountsByGroup {
|
||||
if (!channelId) {
|
||||
return EMPTY_OBJECT;
|
||||
}
|
||||
return state.entities.channels.channelMemberCountsByGroup[channelId] || EMPTY_OBJECT;
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,56 @@ export function getGroupChannels(state: GlobalState, id: string) {
|
||||
return getGroupSyncables(state, id).channels;
|
||||
}
|
||||
|
||||
export const getAllCustomGroups: (state: GlobalState) => Group[] = createSelector(
|
||||
'getAllCustomGroups',
|
||||
getAllGroups,
|
||||
(groups) => {
|
||||
return Object.entries(groups).filter((entry) => (entry[1].allow_reference && entry[1].delete_at === 0 && entry[1].source === GroupSource.Custom)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsAssociatedToTeamForReference: (state: GlobalState, teamID: string) => Group[] = createSelector(
|
||||
'getGroupsAssociatedToTeamForReference',
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => teamGroupIDSet.has(groupID)).filter((entry) => (entry[1].allow_reference && entry[1].delete_at === 0)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
export const getAssociatedGroupsForReference: (state: GlobalState, teamId: string, channelId?: string) => Group[] = createSelector(
|
||||
'getAssociatedGroupsForReference',
|
||||
(state, teamId) => Boolean(getTeam(state, teamId)?.group_constrained),
|
||||
(state, _, channelId) => Boolean(getChannel(state, channelId)?.group_constrained),
|
||||
getGroupsAssociatedToTeamForReference,
|
||||
(state, _, channelId) => (channelId ? getGroupsAssociatedToChannelForReference(state, channelId) : undefined),
|
||||
getAllCustomGroups,
|
||||
(state) => getAllAssociatedGroupsForReference(state, false),
|
||||
(
|
||||
teamConstrained,
|
||||
channelConstrained,
|
||||
groupsFromTeam,
|
||||
groupsFromChannel,
|
||||
customGroups,
|
||||
allGroups,
|
||||
) => {
|
||||
if (teamConstrained && channelConstrained) {
|
||||
const groupSet = new Set<Group>(groupsFromChannel);
|
||||
return [...(groupsFromChannel || []), ...(groupsFromTeam.filter((item) => !groupSet.has(item))), ...customGroups];
|
||||
}
|
||||
|
||||
if (teamConstrained) {
|
||||
return [...customGroups, ...groupsFromTeam];
|
||||
}
|
||||
|
||||
if (channelConstrained) {
|
||||
return [...customGroups, ...(groupsFromChannel || [])];
|
||||
}
|
||||
|
||||
return allGroups;
|
||||
},
|
||||
);
|
||||
|
||||
export const getAssociatedGroupsByName: (state: GlobalState, teamID: string, channelId: string) => Record<string, Group> = createSelector(
|
||||
'getAssociatedGroupsByName',
|
||||
getAssociatedGroupsForReference,
|
||||
@ -100,7 +150,7 @@ export const getAssociatedGroupsByName: (state: GlobalState, teamID: string, cha
|
||||
},
|
||||
);
|
||||
|
||||
export const getAssociatedGroupsForReferenceByMention: (state: GlobalState, teamID: string, channelId: string) => Map<string, Group> = createSelector(
|
||||
export const getAssociatedGroupsForReferenceByMention: (state: GlobalState, teamID: string, channelId?: string) => Map<string, Group> = createSelector(
|
||||
'getAssociatedGroupsForReferenceByMention',
|
||||
getAssociatedGroupsForReference,
|
||||
(groups) => {
|
||||
@ -117,30 +167,6 @@ export function searchAssociatedGroupsForReferenceLocal(state: GlobalState, term
|
||||
return filteredGroups;
|
||||
}
|
||||
|
||||
export function getAssociatedGroupsForReference(state: GlobalState, teamId: string, channelId: string): Group[] {
|
||||
const team = getTeam(state, teamId);
|
||||
const channel = getChannel(state, channelId);
|
||||
|
||||
let groupsForReference = [];
|
||||
if (team && team.group_constrained && channel && channel.group_constrained) {
|
||||
const groupsFromChannel = getGroupsAssociatedToChannelForReference(state, channelId);
|
||||
const groupsFromTeam = getGroupsAssociatedToTeamForReference(state, teamId);
|
||||
const customGroups = getAllCustomGroups(state);
|
||||
groupsForReference = groupsFromChannel.concat(groupsFromTeam.filter((item) => groupsFromChannel.indexOf(item) < 0), customGroups);
|
||||
} else if (team && team.group_constrained) {
|
||||
const customGroups = getAllCustomGroups(state);
|
||||
const groupsFromTeam = getGroupsAssociatedToTeamForReference(state, teamId);
|
||||
groupsForReference = [...customGroups, ...groupsFromTeam];
|
||||
} else if (channel && channel.group_constrained) {
|
||||
const customGroups = getAllCustomGroups(state);
|
||||
const groupsFromChannel = getGroupsAssociatedToChannelForReference(state, channelId);
|
||||
groupsForReference = [...customGroups, ...groupsFromChannel];
|
||||
} else {
|
||||
groupsForReference = getAllAssociatedGroupsForReference(state, false);
|
||||
}
|
||||
return groupsForReference;
|
||||
}
|
||||
|
||||
const teamGroupIDs = (state: GlobalState, teamID: string) => state.entities.teams.groupsAssociatedToTeam[teamID]?.ids || [];
|
||||
|
||||
const channelGroupIDs = (state: GlobalState, channelID: string) => state.entities.channels.groupsAssociatedToChannel[channelID]?.ids || [];
|
||||
@ -209,15 +235,6 @@ export const getGroupsAssociatedToChannel: (state: GlobalState, channelID: strin
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsAssociatedToTeamForReference: (state: GlobalState, teamID: string) => Group[] = createSelector(
|
||||
'getGroupsAssociatedToTeamForReference',
|
||||
getAllGroups,
|
||||
(state: GlobalState, teamID: string) => getTeamGroupIDSet(state, teamID),
|
||||
(allGroups, teamGroupIDSet) => {
|
||||
return Object.entries(allGroups).filter(([groupID]) => teamGroupIDSet.has(groupID)).filter((entry) => (entry[1].allow_reference && entry[1].delete_at === 0)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
export const getGroupsAssociatedToChannelForReference: (state: GlobalState, channelID: string) => Group[] = createSelector(
|
||||
'getGroupsAssociatedToChannelForReference',
|
||||
getAllGroups,
|
||||
@ -264,14 +281,6 @@ export const getAllGroupsForReferenceByName: (state: GlobalState) => Record<stri
|
||||
},
|
||||
);
|
||||
|
||||
export const getAllCustomGroups: (state: GlobalState) => Group[] = createSelector(
|
||||
'getAllCustomGroups',
|
||||
getAllGroups,
|
||||
(groups) => {
|
||||
return Object.entries(groups).filter((entry) => (entry[1].allow_reference && entry[1].delete_at === 0 && entry[1].source === GroupSource.Custom)).map((entry) => entry[1]);
|
||||
},
|
||||
);
|
||||
|
||||
export const makeGetMyAllowReferencedGroups = () => {
|
||||
return createSelector(
|
||||
'makeGetMyAllowReferencedGroups',
|
||||
|
@ -142,7 +142,11 @@ export function makeGetChannelDraft() {
|
||||
const defaultDraft = Object.freeze({message: '', fileInfos: [], uploadsInProgress: [], createAt: 0, updateAt: 0, channelId: '', rootId: ''});
|
||||
const getDraft = makeGetGlobalItemWithDefault(defaultDraft);
|
||||
|
||||
return (state: GlobalState, channelId: string): PostDraft => {
|
||||
return (state: GlobalState, channelId?: string): PostDraft => {
|
||||
if (!channelId) {
|
||||
return defaultDraft;
|
||||
}
|
||||
|
||||
const draft = getDraft(state, StoragePrefixes.DRAFT + channelId);
|
||||
if (
|
||||
typeof draft.message !== 'undefined' &&
|
||||
|
Loading…
Reference in New Issue
Block a user