153 lines
		
	
	
	
		
			5.3 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
	
		
			5.3 KiB
			
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// Copyright 2020-2021 Signal Messenger, LLC
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-only
 | 
						|
 | 
						|
/* eslint-disable react/no-array-index-key */
 | 
						|
 | 
						|
import React from 'react';
 | 
						|
import { createPortal } from 'react-dom';
 | 
						|
import { Avatar } from './Avatar';
 | 
						|
import { ContactName } from './conversation/ContactName';
 | 
						|
import { InContactsIcon } from './InContactsIcon';
 | 
						|
import type { LocalizerType } from '../types/Util';
 | 
						|
import { sortByTitle } from '../util/sortByTitle';
 | 
						|
import type { ConversationType } from '../state/ducks/conversations';
 | 
						|
import { isInSystemContacts } from '../util/isInSystemContacts';
 | 
						|
 | 
						|
type ParticipantType = ConversationType & {
 | 
						|
  hasRemoteAudio?: boolean;
 | 
						|
  hasRemoteVideo?: boolean;
 | 
						|
  presenting?: boolean;
 | 
						|
};
 | 
						|
 | 
						|
export type PropsType = {
 | 
						|
  readonly i18n: LocalizerType;
 | 
						|
  readonly onClose: () => void;
 | 
						|
  readonly ourUuid: string;
 | 
						|
  readonly participants: Array<ParticipantType>;
 | 
						|
};
 | 
						|
 | 
						|
export const CallingParticipantsList = React.memo(
 | 
						|
  ({ i18n, onClose, ourUuid, participants }: PropsType) => {
 | 
						|
    const [root, setRoot] = React.useState<HTMLElement | null>(null);
 | 
						|
 | 
						|
    const sortedParticipants = React.useMemo<Array<ParticipantType>>(
 | 
						|
      () => sortByTitle(participants),
 | 
						|
      [participants]
 | 
						|
    );
 | 
						|
 | 
						|
    React.useEffect(() => {
 | 
						|
      const div = document.createElement('div');
 | 
						|
      document.body.appendChild(div);
 | 
						|
      setRoot(div);
 | 
						|
 | 
						|
      return () => {
 | 
						|
        document.body.removeChild(div);
 | 
						|
        setRoot(null);
 | 
						|
      };
 | 
						|
    }, []);
 | 
						|
 | 
						|
    const handleCancel = React.useCallback(
 | 
						|
      (e: React.MouseEvent) => {
 | 
						|
        if (e.target === e.currentTarget) {
 | 
						|
          onClose();
 | 
						|
        }
 | 
						|
      },
 | 
						|
      [onClose]
 | 
						|
    );
 | 
						|
 | 
						|
    if (!root) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return createPortal(
 | 
						|
      <div
 | 
						|
        className="module-calling-participants-list__overlay"
 | 
						|
        onClick={handleCancel}
 | 
						|
        role="presentation"
 | 
						|
      >
 | 
						|
        <div className="module-calling-participants-list">
 | 
						|
          <div className="module-calling-participants-list__header">
 | 
						|
            <div className="module-calling-participants-list__title">
 | 
						|
              {!participants.length && i18n('calling__in-this-call--zero')}
 | 
						|
              {participants.length === 1 && i18n('calling__in-this-call--one')}
 | 
						|
              {participants.length > 1 &&
 | 
						|
                i18n('calling__in-this-call--many', [
 | 
						|
                  String(participants.length),
 | 
						|
                ])}
 | 
						|
            </div>
 | 
						|
            <button
 | 
						|
              type="button"
 | 
						|
              className="module-calling-participants-list__close"
 | 
						|
              onClick={onClose}
 | 
						|
              tabIndex={0}
 | 
						|
              aria-label={i18n('close')}
 | 
						|
            />
 | 
						|
          </div>
 | 
						|
          <ul className="module-calling-participants-list__list">
 | 
						|
            {sortedParticipants.map(
 | 
						|
              (participant: ParticipantType, index: number) => (
 | 
						|
                <li
 | 
						|
                  className="module-calling-participants-list__contact"
 | 
						|
                  // It's tempting to use `participant.uuid` as the `key` here, but that
 | 
						|
                  //   can result in duplicate keys for participants who have joined on
 | 
						|
                  //   multiple devices.
 | 
						|
                  key={index}
 | 
						|
                >
 | 
						|
                  <div>
 | 
						|
                    <Avatar
 | 
						|
                      acceptedMessageRequest={
 | 
						|
                        participant.acceptedMessageRequest
 | 
						|
                      }
 | 
						|
                      avatarPath={participant.avatarPath}
 | 
						|
                      color={participant.color}
 | 
						|
                      conversationType="direct"
 | 
						|
                      i18n={i18n}
 | 
						|
                      isMe={participant.isMe}
 | 
						|
                      profileName={participant.profileName}
 | 
						|
                      title={participant.title}
 | 
						|
                      sharedGroupNames={participant.sharedGroupNames}
 | 
						|
                      size={32}
 | 
						|
                    />
 | 
						|
                    {participant.uuid === ourUuid ? (
 | 
						|
                      <span className="module-calling-participants-list__name">
 | 
						|
                        {i18n('you')}
 | 
						|
                      </span>
 | 
						|
                    ) : (
 | 
						|
                      <>
 | 
						|
                        <ContactName
 | 
						|
                          module="module-calling-participants-list__name"
 | 
						|
                          title={participant.title}
 | 
						|
                        />
 | 
						|
                        {isInSystemContacts(participant) ? (
 | 
						|
                          <span>
 | 
						|
                            {' '}
 | 
						|
                            <InContactsIcon
 | 
						|
                              className="module-calling-participants-list__contact-icon"
 | 
						|
                              i18n={i18n}
 | 
						|
                            />
 | 
						|
                          </span>
 | 
						|
                        ) : null}
 | 
						|
                      </>
 | 
						|
                    )}
 | 
						|
                  </div>
 | 
						|
                  <div>
 | 
						|
                    {participant.hasRemoteAudio === false ? (
 | 
						|
                      <span className="module-calling-participants-list__muted--audio" />
 | 
						|
                    ) : null}
 | 
						|
                    {participant.hasRemoteVideo === false ? (
 | 
						|
                      <span className="module-calling-participants-list__muted--video" />
 | 
						|
                    ) : null}
 | 
						|
                    {participant.presenting ? (
 | 
						|
                      <span className="module-calling-participants-list__presenting" />
 | 
						|
                    ) : null}
 | 
						|
                  </div>
 | 
						|
                </li>
 | 
						|
              )
 | 
						|
            )}
 | 
						|
          </ul>
 | 
						|
        </div>
 | 
						|
      </div>,
 | 
						|
      root
 | 
						|
    );
 | 
						|
  }
 | 
						|
);
 |