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:
Daniel Espino García 2024-07-11 12:58:56 +02:00 committed by GitHub
parent 19d59d1126
commit e8b4892877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 162 additions and 159 deletions

View File

@ -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]);

View File

@ -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 (

View File

@ -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;
}

View File

@ -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();

View File

@ -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);

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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',

View File

@ -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' &&