New Group administration: Add users
This commit is contained in:
parent
e81c18e84c
commit
b81a52bbdd
43 changed files with 1789 additions and 277 deletions
|
@ -35,6 +35,7 @@ import {
|
|||
getGroupSizeRecommendedLimit,
|
||||
getGroupSizeHardLimit,
|
||||
} from '../../groups/limits';
|
||||
import { toggleSelectedContactForGroupAddition } from '../../groups/toggleSelectedContactForGroupAddition';
|
||||
|
||||
// State
|
||||
|
||||
|
@ -2273,50 +2274,23 @@ export function reducer(
|
|||
return state;
|
||||
}
|
||||
|
||||
const { selectedConversationIds: oldSelectedConversationIds } = composer;
|
||||
let {
|
||||
maximumGroupSizeModalState,
|
||||
recommendedGroupSizeModalState,
|
||||
} = composer;
|
||||
const {
|
||||
conversationId,
|
||||
maxGroupSize,
|
||||
maxRecommendedGroupSize,
|
||||
} = action.payload;
|
||||
|
||||
const selectedConversationIds = without(
|
||||
oldSelectedConversationIds,
|
||||
conversationId
|
||||
);
|
||||
const shouldAdd =
|
||||
selectedConversationIds.length === oldSelectedConversationIds.length;
|
||||
if (shouldAdd) {
|
||||
// 1 for you, 1 for the new contact.
|
||||
const newExpectedMemberCount = selectedConversationIds.length + 2;
|
||||
if (newExpectedMemberCount > maxGroupSize) {
|
||||
return state;
|
||||
}
|
||||
if (
|
||||
newExpectedMemberCount === maxGroupSize &&
|
||||
maximumGroupSizeModalState === OneTimeModalState.NeverShown
|
||||
) {
|
||||
maximumGroupSizeModalState = OneTimeModalState.Showing;
|
||||
} else if (
|
||||
newExpectedMemberCount >= maxRecommendedGroupSize &&
|
||||
recommendedGroupSizeModalState === OneTimeModalState.NeverShown
|
||||
) {
|
||||
recommendedGroupSizeModalState = OneTimeModalState.Showing;
|
||||
}
|
||||
selectedConversationIds.push(conversationId);
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
composer: {
|
||||
...composer,
|
||||
maximumGroupSizeModalState,
|
||||
recommendedGroupSizeModalState,
|
||||
selectedConversationIds,
|
||||
...toggleSelectedContactForGroupAddition(
|
||||
action.payload.conversationId,
|
||||
{
|
||||
maxGroupSize: action.payload.maxGroupSize,
|
||||
maxRecommendedGroupSize: action.payload.maxRecommendedGroupSize,
|
||||
maximumGroupSizeModalState: composer.maximumGroupSizeModalState,
|
||||
// We say you're already in the group, even though it hasn't been created yet.
|
||||
numberOfContactsAlreadyInGroup: 1,
|
||||
recommendedGroupSizeModalState:
|
||||
composer.recommendedGroupSizeModalState,
|
||||
selectedConversationIds: composer.selectedConversationIds,
|
||||
}
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import memoizee from 'memoizee';
|
||||
import { fromPairs, isNumber, isString } from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import Fuse, { FuseOptions } from 'fuse.js';
|
||||
|
||||
import { StateType } from '../reducer';
|
||||
import {
|
||||
|
@ -29,6 +28,7 @@ import { PropsDataType as TimelinePropsType } from '../../components/conversatio
|
|||
import { TimelineItemType } from '../../components/conversation/TimelineItem';
|
||||
import { assert } from '../../util/assert';
|
||||
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
|
||||
import { filterAndSortContacts } from '../../util/filterAndSortContacts';
|
||||
|
||||
import {
|
||||
getInteractionMode,
|
||||
|
@ -342,14 +342,14 @@ export const getComposerContactSearchTerm = createSelector(
|
|||
);
|
||||
|
||||
/**
|
||||
* This returns contacts for the composer, which isn't just your primary's system
|
||||
* contacts. It may include false positives, which is better than missing contacts.
|
||||
* This returns contacts for the composer and group members, which isn't just your primary
|
||||
* system contacts. It may include false positives, which is better than missing contacts.
|
||||
*
|
||||
* Because it filters unregistered contacts and that's (partially) determined by the
|
||||
* current time, it's possible for this to return stale contacts that have unregistered
|
||||
* if no other conversations change. This should be a rare false positive.
|
||||
*/
|
||||
const getContacts = createSelector(
|
||||
export const getContacts = createSelector(
|
||||
getConversationLookup,
|
||||
(conversationLookup: ConversationLookupType): Array<ConversationType> =>
|
||||
Object.values(conversationLookup).filter(
|
||||
|
@ -371,13 +371,6 @@ const getNoteToSelfTitle = createSelector(getIntl, (i18n: LocalizerType) =>
|
|||
i18n('noteToSelf').toLowerCase()
|
||||
);
|
||||
|
||||
const COMPOSE_CONTACTS_FUSE_OPTIONS: FuseOptions<ConversationType> = {
|
||||
// A small-but-nonzero threshold lets us match parts of E164s better, and makes the
|
||||
// search a little more forgiving.
|
||||
threshold: 0.05,
|
||||
keys: ['title', 'name', 'e164'],
|
||||
};
|
||||
|
||||
export const getComposeContacts = createSelector(
|
||||
getNormalizedComposerContactSearchTerm,
|
||||
getContacts,
|
||||
|
@ -389,55 +382,21 @@ export const getComposeContacts = createSelector(
|
|||
noteToSelf: ConversationType,
|
||||
noteToSelfTitle: string
|
||||
): Array<ConversationType> => {
|
||||
let result: Array<ConversationType>;
|
||||
|
||||
if (searchTerm.length) {
|
||||
const fuse = new Fuse<ConversationType>(
|
||||
contacts,
|
||||
COMPOSE_CONTACTS_FUSE_OPTIONS
|
||||
);
|
||||
result = fuse.search(searchTerm);
|
||||
if (noteToSelfTitle.includes(searchTerm)) {
|
||||
result.push(noteToSelf);
|
||||
}
|
||||
} else {
|
||||
result = contacts.concat();
|
||||
result.sort((a, b) => collator.compare(a.title, b.title));
|
||||
const result: Array<ConversationType> = filterAndSortContacts(
|
||||
contacts,
|
||||
searchTerm
|
||||
);
|
||||
if (!searchTerm || noteToSelfTitle.includes(searchTerm)) {
|
||||
result.push(noteToSelf);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* This returns contacts for the composer when you're picking new group members. It casts
|
||||
* a wider net than `getContacts`.
|
||||
*/
|
||||
const getGroupContacts = createSelector(
|
||||
getConversationLookup,
|
||||
(conversationLookup): Array<ConversationType> =>
|
||||
Object.values(conversationLookup).filter(
|
||||
contact =>
|
||||
contact.type === 'direct' &&
|
||||
!contact.isMe &&
|
||||
!contact.isBlocked &&
|
||||
!isConversationUnregistered(contact)
|
||||
)
|
||||
);
|
||||
|
||||
export const getCandidateGroupContacts = createSelector(
|
||||
export const getCandidateContactsForNewGroup = createSelector(
|
||||
getContacts,
|
||||
getNormalizedComposerContactSearchTerm,
|
||||
getGroupContacts,
|
||||
(searchTerm, contacts): Array<ConversationType> => {
|
||||
if (searchTerm.length) {
|
||||
return new Fuse<ConversationType>(
|
||||
contacts,
|
||||
COMPOSE_CONTACTS_FUSE_OPTIONS
|
||||
).search(searchTerm);
|
||||
}
|
||||
return contacts.concat().sort((a, b) => collator.compare(a.title, b.title));
|
||||
}
|
||||
filterAndSortContacts
|
||||
);
|
||||
|
||||
export const getCantAddContactForModal = createSelector(
|
||||
|
|
|
@ -8,11 +8,15 @@ import {
|
|||
ConversationDetails,
|
||||
StateProps,
|
||||
} from '../../components/conversation/conversation-details/ConversationDetails';
|
||||
import { getConversationSelector } from '../selectors/conversations';
|
||||
import {
|
||||
getContacts,
|
||||
getConversationSelector,
|
||||
} from '../selectors/conversations';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import { MediaItemType } from '../../components/LightboxGallery';
|
||||
|
||||
export type SmartConversationDetailsProps = {
|
||||
addMembers: (conversationIds: ReadonlyArray<string>) => Promise<void>;
|
||||
conversationId: string;
|
||||
hasGroupLink: boolean;
|
||||
loadRecentMediaItems: (limit: number) => void;
|
||||
|
@ -46,10 +50,12 @@ const mapStateToProps = (
|
|||
? conversation.canEditGroupInfo
|
||||
: false;
|
||||
const isAdmin = Boolean(conversation?.areWeAdmin);
|
||||
const candidateContactsToAdd = getContacts(state);
|
||||
|
||||
return {
|
||||
...props,
|
||||
canEditGroupInfo,
|
||||
candidateContactsToAdd,
|
||||
conversation,
|
||||
i18n: getIntl(state),
|
||||
isAdmin,
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ComposerStep, OneTimeModalState } from '../ducks/conversations';
|
|||
import { getSearchResults, isSearching } from '../selectors/search';
|
||||
import { getIntl, getRegionCode } from '../selectors/user';
|
||||
import {
|
||||
getCandidateGroupContacts,
|
||||
getCandidateContactsForNewGroup,
|
||||
getCantAddContactForModal,
|
||||
getComposeContacts,
|
||||
getComposeGroupAvatar,
|
||||
|
@ -102,7 +102,7 @@ const getModeSpecificProps = (
|
|||
case ComposerStep.ChooseGroupMembers:
|
||||
return {
|
||||
mode: LeftPaneMode.ChooseGroupMembers,
|
||||
candidateContacts: getCandidateGroupContacts(state),
|
||||
candidateContacts: getCandidateContactsForNewGroup(state),
|
||||
cantAddContactForModal: getCantAddContactForModal(state),
|
||||
isShowingRecommendedGroupSizeModal:
|
||||
getRecommendedGroupSizeModalState(state) ===
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue