// Copyright 2020-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import { times } from 'lodash'; import { v4 as generateUuid } from 'uuid'; import { storiesOf } from '@storybook/react'; import { boolean, select, number } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { CallMode, CallState, GroupCallConnectionState, GroupCallJoinState, GroupCallRemoteParticipantType, } from '../types/Calling'; import { ConversationType } from '../state/ducks/conversations'; import { Colors } from '../types/Colors'; import { CallScreen, PropsType } from './CallScreen'; import { setup as setupI18n } from '../../js/modules/i18n'; import { missingCaseError } from '../util/missingCaseError'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource'; import enMessages from '../../_locales/en/messages.json'; const MAX_PARTICIPANTS = 32; const i18n = setupI18n('en', enMessages); const conversation = { id: '3051234567', avatarPath: undefined, color: Colors[0], title: 'Rick Sanchez', name: 'Rick Sanchez', phoneNumber: '3051234567', profileName: 'Rick Sanchez', markedUnread: false, type: 'direct' as const, lastUpdated: Date.now(), }; type OverridePropsBase = { hasLocalAudio?: boolean; hasLocalVideo?: boolean; isInSpeakerView?: boolean; }; type DirectCallOverrideProps = OverridePropsBase & { callMode: CallMode.Direct; callState?: CallState; hasRemoteVideo?: boolean; }; type GroupCallOverrideProps = OverridePropsBase & { callMode: CallMode.Group; connectionState?: GroupCallConnectionState; peekedParticipants?: Array; remoteParticipants?: Array; }; const createActiveDirectCallProp = ( overrideProps: DirectCallOverrideProps ) => ({ callMode: CallMode.Direct as CallMode.Direct, conversation, callState: select( 'callState', CallState, overrideProps.callState || CallState.Accepted ), peekedParticipants: [] as [], remoteParticipants: [ { hasRemoteVideo: boolean( 'hasRemoteVideo', Boolean(overrideProps.hasRemoteVideo) ), }, ] as [ { hasRemoteVideo: boolean; } ], }); const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({ callMode: CallMode.Group as CallMode.Group, connectionState: overrideProps.connectionState || GroupCallConnectionState.Connected, conversationsWithSafetyNumberChanges: [], joinState: GroupCallJoinState.Joined, maxDevices: 5, deviceCount: (overrideProps.remoteParticipants || []).length, // Because remote participants are a superset, we can use them in place of peeked // participants. peekedParticipants: overrideProps.peekedParticipants || overrideProps.remoteParticipants || [], remoteParticipants: overrideProps.remoteParticipants || [], }); const createActiveCallProp = ( overrideProps: DirectCallOverrideProps | GroupCallOverrideProps ) => { const baseResult = { joinedAt: Date.now(), conversation, hasLocalAudio: boolean( 'hasLocalAudio', overrideProps.hasLocalAudio || false ), hasLocalVideo: boolean( 'hasLocalVideo', overrideProps.hasLocalVideo || false ), isInSpeakerView: boolean( 'isInSpeakerView', overrideProps.isInSpeakerView || false ), pip: false, settingsDialogOpen: false, showParticipantsList: false, }; switch (overrideProps.callMode) { case CallMode.Direct: return { ...baseResult, ...createActiveDirectCallProp(overrideProps) }; case CallMode.Group: return { ...baseResult, ...createActiveGroupCallProp(overrideProps) }; default: throw missingCaseError(overrideProps); } }; const createProps = ( overrideProps: DirectCallOverrideProps | GroupCallOverrideProps = { callMode: CallMode.Direct as CallMode.Direct, } ): PropsType => ({ activeCall: createActiveCallProp(overrideProps), getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource, hangUp: action('hang-up'), i18n, me: { color: Colors[1], name: 'Morty Smith', profileName: 'Morty Smith', title: 'Morty Smith', }, setGroupCallVideoRequest: action('set-group-call-video-request'), setLocalAudio: action('set-local-audio'), setLocalPreview: action('set-local-preview'), setLocalVideo: action('set-local-video'), setRendererCanvas: action('set-renderer-canvas'), stickyControls: boolean('stickyControls', false), toggleParticipants: action('toggle-participants'), togglePip: action('toggle-pip'), toggleSettings: action('toggle-settings'), toggleSpeakerView: action('toggle-speaker-view'), }); const story = storiesOf('Components/CallScreen', module); story.add('Default', () => { return ; }); story.add('Pre-Ring', () => { return ( ); }); story.add('Ringing', () => { return ( ); }); story.add('Reconnecting', () => { return ( ); }); story.add('Ended', () => { return ( ); }); story.add('hasLocalAudio', () => { return ( ); }); story.add('hasLocalVideo', () => { return ( ); }); story.add('hasRemoteVideo', () => { return ( ); }); story.add('Group call - 1', () => ( )); // We generate these upfront so that the list is stable when you move the slider. const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => ({ demuxId: index, hasRemoteAudio: index % 3 !== 0, hasRemoteVideo: index % 4 !== 0, videoAspectRatio: 1.3, ...getDefaultConversation({ isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1, title: `Participant ${index + 1}`, uuid: generateUuid(), }), })); story.add('Group call - Many', () => { return ( ); }); story.add('Group call - reconnecting', () => ( )); story.add('Group call - 0', () => ( ));