You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			232 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			232 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			TypeScript
		
	
import React, { useState } from 'react';
 | 
						|
 | 
						|
import { SessionButton, SessionButtonColor } from '../session/SessionButton';
 | 
						|
import { ContactType, SessionMemberListItem } from '../session/SessionMemberListItem';
 | 
						|
import { getConversationController } from '../../session/conversations';
 | 
						|
import { ToastUtils, UserUtils } from '../../session/utils';
 | 
						|
import { initiateGroupUpdate } from '../../session/group';
 | 
						|
import { ConversationModel, ConversationTypeEnum } from '../../models/conversation';
 | 
						|
import { getCompleteUrlForV2ConvoId } from '../../interactions/conversationInteractions';
 | 
						|
import _ from 'lodash';
 | 
						|
import { VALIDATION } from '../../session/constants';
 | 
						|
import { SessionWrapperModal } from '../session/SessionWrapperModal';
 | 
						|
import { SpacerLG } from '../basic/Text';
 | 
						|
import { useDispatch } from 'react-redux';
 | 
						|
import { updateInviteContactModal } from '../../state/ducks/modalDialog';
 | 
						|
// tslint:disable-next-line: no-submodule-imports
 | 
						|
import useKey from 'react-use/lib/useKey';
 | 
						|
 | 
						|
type Props = {
 | 
						|
  conversationId: string;
 | 
						|
};
 | 
						|
 | 
						|
const InviteContactsDialogInner = (props: Props) => {
 | 
						|
  const { conversationId } = props;
 | 
						|
 | 
						|
  const dispatch = useDispatch();
 | 
						|
 | 
						|
  const convo = getConversationController().get(conversationId);
 | 
						|
  // tslint:disable-next-line: max-func-body-length
 | 
						|
 | 
						|
  let contacts = getConversationController()
 | 
						|
    .getConversations()
 | 
						|
    .filter(d => !!d && !d.isBlocked() && d.isPrivate() && !d.isMe() && !!d.get('active_at'));
 | 
						|
  if (!convo.isPublic()) {
 | 
						|
    // filter our zombies and current members from the list of contact we can add
 | 
						|
 | 
						|
    const members = convo.get('members') || [];
 | 
						|
    const zombies = convo.get('zombies') || [];
 | 
						|
    contacts = contacts.filter(d => !members.includes(d.id) && !zombies.includes(d.id));
 | 
						|
  }
 | 
						|
 | 
						|
  const chatName = convo.get('name');
 | 
						|
  const isPublicConvo = convo.isPublic();
 | 
						|
 | 
						|
  const [contactList, setContactList] = useState(
 | 
						|
    contacts.map((d: ConversationModel) => {
 | 
						|
      const lokiProfile = d.getLokiProfile();
 | 
						|
      const nickname = d.getNickname();
 | 
						|
      const name = nickname
 | 
						|
        ? nickname
 | 
						|
        : lokiProfile
 | 
						|
        ? lokiProfile.displayName
 | 
						|
        : window.i18n('anonymous');
 | 
						|
 | 
						|
      // TODO: should take existing members into account
 | 
						|
      const existingMember = false;
 | 
						|
 | 
						|
      return {
 | 
						|
        id: d.id,
 | 
						|
        authorPhoneNumber: d.id,
 | 
						|
        authorProfileName: name,
 | 
						|
        authorAvatarPath: d?.getAvatarPath() || '',
 | 
						|
        selected: false,
 | 
						|
        authorName: name,
 | 
						|
        checkmarked: false,
 | 
						|
        existingMember,
 | 
						|
      };
 | 
						|
    })
 | 
						|
  );
 | 
						|
 | 
						|
  const closeDialog = () => {
 | 
						|
    dispatch(updateInviteContactModal(null));
 | 
						|
  };
 | 
						|
 | 
						|
  const onClickOK = () => {
 | 
						|
    const selectedContacts = contactList
 | 
						|
      .filter((d: ContactType) => d.checkmarked)
 | 
						|
      .map((d: ContactType) => d.id);
 | 
						|
 | 
						|
    if (selectedContacts.length > 0) {
 | 
						|
      if (isPublicConvo) {
 | 
						|
        void submitForOpenGroup(selectedContacts);
 | 
						|
      } else {
 | 
						|
        void submitForClosedGroup(selectedContacts);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    closeDialog();
 | 
						|
  };
 | 
						|
 | 
						|
  useKey((event: KeyboardEvent) => {
 | 
						|
    return event.key === 'Enter';
 | 
						|
  }, onClickOK);
 | 
						|
 | 
						|
  useKey((event: KeyboardEvent) => {
 | 
						|
    return event.key === 'Esc' || event.key === 'Escape';
 | 
						|
  }, closeDialog);
 | 
						|
 | 
						|
  const titleText = `${window.i18n('addingContacts')} ${chatName}`;
 | 
						|
  const cancelText = window.i18n('cancel');
 | 
						|
  const okText = window.i18n('ok');
 | 
						|
 | 
						|
  const hasContacts = contactList.length !== 0;
 | 
						|
 | 
						|
  const submitForOpenGroup = async (pubkeys: Array<string>) => {
 | 
						|
    if (convo.isOpenGroupV2()) {
 | 
						|
      const completeUrl = await getCompleteUrlForV2ConvoId(convo.id);
 | 
						|
      const groupInvitation = {
 | 
						|
        url: completeUrl,
 | 
						|
        name: convo.getName(),
 | 
						|
      };
 | 
						|
      pubkeys.forEach(async pubkeyStr => {
 | 
						|
        const privateConvo = await getConversationController().getOrCreateAndWait(
 | 
						|
          pubkeyStr,
 | 
						|
          ConversationTypeEnum.PRIVATE
 | 
						|
        );
 | 
						|
 | 
						|
        if (privateConvo) {
 | 
						|
          void privateConvo.sendMessage({
 | 
						|
            body: '',
 | 
						|
            attachments: undefined,
 | 
						|
            groupInvitation,
 | 
						|
            preview: undefined,
 | 
						|
            quote: undefined,
 | 
						|
          });
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const submitForClosedGroup = async (pubkeys: Array<string>) => {
 | 
						|
    // closed group chats
 | 
						|
    const ourPK = UserUtils.getOurPubKeyStrFromCache();
 | 
						|
    // we only care about real members. If a member is currently a zombie we have to be able to add him back
 | 
						|
    let existingMembers = convo.get('members') || [];
 | 
						|
    // at least make sure it's an array
 | 
						|
    if (!Array.isArray(existingMembers)) {
 | 
						|
      existingMembers = [];
 | 
						|
    }
 | 
						|
    existingMembers = _.compact(existingMembers);
 | 
						|
    const existingZombies = convo.get('zombies') || [];
 | 
						|
    const newMembers = pubkeys.filter(d => !existingMembers.includes(d));
 | 
						|
 | 
						|
    if (newMembers.length > 0) {
 | 
						|
      // Do not trigger an update if there is too many members
 | 
						|
      // be sure to include current zombies in this count
 | 
						|
      if (
 | 
						|
        newMembers.length + existingMembers.length + existingZombies.length >
 | 
						|
        VALIDATION.CLOSED_GROUP_SIZE_LIMIT
 | 
						|
      ) {
 | 
						|
        ToastUtils.pushTooManyMembers();
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      const allMembers = _.concat(existingMembers, newMembers, [ourPK]);
 | 
						|
      const uniqMembers = _.uniq(allMembers);
 | 
						|
 | 
						|
      const groupId = convo.get('id');
 | 
						|
      const groupName = convo.get('name');
 | 
						|
 | 
						|
      await initiateGroupUpdate(
 | 
						|
        groupId,
 | 
						|
        groupName || window.i18n('unknown'),
 | 
						|
        uniqMembers,
 | 
						|
        undefined
 | 
						|
      );
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
  const renderMemberList = () => {
 | 
						|
    const members = contactList;
 | 
						|
    const selectedContacts = contactList
 | 
						|
      .filter((d: ContactType) => d.checkmarked)
 | 
						|
      .map((d: ContactType) => d.id);
 | 
						|
 | 
						|
    return members.map((member: ContactType, index: number) => (
 | 
						|
      <SessionMemberListItem
 | 
						|
        member={member}
 | 
						|
        key={member.id}
 | 
						|
        index={index}
 | 
						|
        isSelected={selectedContacts.some(m => m === member.id)}
 | 
						|
        onSelect={(selectedMember: ContactType) => {
 | 
						|
          onMemberClicked(selectedMember);
 | 
						|
        }}
 | 
						|
        onUnselect={(selectedMember: ContactType) => {
 | 
						|
          onMemberClicked(selectedMember);
 | 
						|
        }}
 | 
						|
      />
 | 
						|
    ));
 | 
						|
  };
 | 
						|
 | 
						|
  const onMemberClicked = (clickedMember: ContactType) => {
 | 
						|
    const updatedContacts = contactList.map((member: ContactType) => {
 | 
						|
      if (member.id === clickedMember.id) {
 | 
						|
        return { ...member, checkmarked: !member.checkmarked };
 | 
						|
      } else {
 | 
						|
        return member;
 | 
						|
      }
 | 
						|
    });
 | 
						|
    setContactList(updatedContacts);
 | 
						|
  };
 | 
						|
 | 
						|
  return (
 | 
						|
    <SessionWrapperModal title={titleText} onClose={closeDialog}>
 | 
						|
      <SpacerLG />
 | 
						|
 | 
						|
      <div className="contact-selection-list">{renderMemberList()}</div>
 | 
						|
      {hasContacts ? null : (
 | 
						|
        <>
 | 
						|
          <SpacerLG />
 | 
						|
          <p className="no-contacts">{window.i18n('noContactsToAdd')}</p>
 | 
						|
          <SpacerLG />
 | 
						|
        </>
 | 
						|
      )}
 | 
						|
 | 
						|
      <SpacerLG />
 | 
						|
 | 
						|
      <div className="session-modal__button-group">
 | 
						|
        <SessionButton text={cancelText} onClick={closeDialog} />
 | 
						|
        <SessionButton
 | 
						|
          text={okText}
 | 
						|
          disabled={!hasContacts}
 | 
						|
          onClick={onClickOK}
 | 
						|
          buttonColor={SessionButtonColor.Green}
 | 
						|
        />
 | 
						|
      </div>
 | 
						|
    </SessionWrapperModal>
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
export const InviteContactsDialog = InviteContactsDialogInner;
 |