diff --git a/webapp/channels/src/components/common/hooks/useGetSelfHostedProducts.ts b/webapp/channels/src/components/common/hooks/useGetSelfHostedProducts.ts index ded117d318..d2bab4779d 100644 --- a/webapp/channels/src/components/common/hooks/useGetSelfHostedProducts.ts +++ b/webapp/channels/src/components/common/hooks/useGetSelfHostedProducts.ts @@ -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, 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, boolean] = useMemo(() => { return [products, productsReceived]; }, [products, productsReceived]); diff --git a/webapp/channels/src/components/compass_theme_provider/compass_theme_provider.tsx b/webapp/channels/src/components/compass_theme_provider/compass_theme_provider.tsx index 58825125c3..1e5eea4006 100644 --- a/webapp/channels/src/components/compass_theme_provider/compass_theme_provider.tsx +++ b/webapp/channels/src/components/compass_theme_provider/compass_theme_provider.tsx @@ -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 ( diff --git a/webapp/channels/src/components/onboarding_tasklist/onboarding_tasklist.tsx b/webapp/channels/src/components/onboarding_tasklist/onboarding_tasklist.tsx index 10ab824ad1..896ace0cb6 100644 --- a/webapp/channels/src/components/onboarding_tasklist/onboarding_tasklist.tsx +++ b/webapp/channels/src/components/onboarding_tasklist/onboarding_tasklist.tsx @@ -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; } diff --git a/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category_menu/index.tsx b/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category_menu/index.tsx index f22f809b04..bd3641dda5 100644 --- a/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category_menu/index.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category_menu/index.tsx @@ -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(); diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/index.ts b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/index.ts index e94c5ab2d2..cd0414982e 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/index.ts +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/index.ts @@ -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); diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/sidebar_channel_menu.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/sidebar_channel_menu.tsx index 91860f5e3f..34f8ea5948 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/sidebar_channel_menu.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_menu/sidebar_channel_menu.tsx @@ -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 = ( } 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 = ( } 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 = ( } 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 = ( } labels={( @@ -120,14 +138,14 @@ const SidebarChannelMenu = (props: Props) => { } let muteUnmuteChannelMenuItem: JSX.Element | null = null; - if (props.isMuted) { + if (isMuted) { let muteChannelText = ( ); - 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 = ( { } function handleUnmuteChannel() { - props.unmuteChannel(props.currentUserId, props.channel.id); + unmuteChannel(currentUserId, channel.id); } muteUnmuteChannelMenuItem = ( } 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 = ( { } function handleMuteChannel() { - props.muteChannel(props.currentUserId, props.channel.id); + muteChannel(currentUserId, channel.id); } muteUnmuteChannelMenuItem = ( } 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 = ( } 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 = ( } @@ -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 = ( ); - 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 = ( { } 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 = ( } labels={leaveChannelText} @@ -270,30 +288,30 @@ const SidebarChannelMenu = (props: Props) => { return ( , }} 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} - + {(copyLinkMenuItem || addMembersMenuItem) && } {copyLinkMenuItem} {addMembersMenuItem} diff --git a/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx b/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx index d13c13c567..8b87eaf21a 100644 --- a/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx @@ -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) { return (
) { return (
) { return (
) { return (
{ 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 diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.test.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.test.ts index 02f0d1bb7c..fe5c026b06 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.test.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.test.ts @@ -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(); diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.ts index d06c007621..3e2d7db401 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channel_categories.ts @@ -447,13 +447,18 @@ export function makeGetChannelsByCategory() { const channelsByCategory: RelationOneToOne = {}; + // 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; } diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts index 6f13671d96..6bd03b7a32 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/channels.ts @@ -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; } diff --git a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/groups.ts b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/groups.ts index 77ac6920d9..87c93c9e79 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/groups.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/selectors/entities/groups.ts @@ -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(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 = 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 = createSelector( +export const getAssociatedGroupsForReferenceByMention: (state: GlobalState, teamID: string, channelId?: string) => Map = 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 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', diff --git a/webapp/channels/src/selectors/rhs.ts b/webapp/channels/src/selectors/rhs.ts index bb6dfe55c0..9c8a49e4ea 100644 --- a/webapp/channels/src/selectors/rhs.ts +++ b/webapp/channels/src/selectors/rhs.ts @@ -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' &&