// Copyright 2020-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; import { times } from 'lodash'; import { storiesOf } from '@storybook/react'; import { boolean, select, number } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import type { GroupCallRemoteParticipantType } from '../types/Calling'; import { CallMode, CallState, GroupCallConnectionState, GroupCallJoinState, } from '../types/Calling'; import type { ConversationType } from '../state/ducks/conversations'; import { AvatarColors } from '../types/Colors'; import type { PropsType } from './CallScreen'; import { CallScreen } from './CallScreen'; import { setupI18n } from '../util/setupI18n'; import { missingCaseError } from '../util/missingCaseError'; import { getDefaultConversation, getDefaultConversationWithUuid, } from '../test-both/helpers/getDefaultConversation'; import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource'; import enMessages from '../../_locales/en/messages.json'; const MAX_PARTICIPANTS = 64; const i18n = setupI18n('en', enMessages); const conversation = getDefaultConversation({ id: '3051234567', avatarPath: undefined, color: AvatarColors[0], title: 'Rick Sanchez', name: 'Rick Sanchez', phoneNumber: '3051234567', profileName: 'Rick Sanchez', }); type OverridePropsBase = { hasLocalAudio?: boolean; hasLocalVideo?: boolean; amISpeaking?: 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) ), presenting: false, title: 'test', }, ] as [ { hasRemoteVideo: boolean; presenting: boolean; title: string; } ], }); 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, groupMembers: overrideProps.remoteParticipants || [], // Because remote participants are a superset, we can use them in place of peeked // participants. peekedParticipants: overrideProps.peekedParticipants || overrideProps.remoteParticipants || [], remoteParticipants: overrideProps.remoteParticipants || [], speakingDemuxIds: new Set(), }); const createActiveCallProp = ( overrideProps: DirectCallOverrideProps | GroupCallOverrideProps ) => { const baseResult = { joinedAt: Date.now(), conversation, hasLocalAudio: boolean( 'hasLocalAudio', overrideProps.hasLocalAudio || false ), hasLocalVideo: boolean( 'hasLocalVideo', overrideProps.hasLocalVideo || false ), amISpeaking: boolean('amISpeaking', overrideProps.amISpeaking || false), isInSpeakerView: boolean( 'isInSpeakerView', overrideProps.isInSpeakerView || false ), outgoingRing: true, 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, getPresentingSources: action('get-presenting-sources'), hangUpActiveCall: action('hang-up'), i18n, me: getDefaultConversation({ color: AvatarColors[1], id: '6146087e-f7ef-457e-9a8d-47df1fdd6b25', name: 'Morty Smith', profileName: 'Morty Smith', title: 'Morty Smith', uuid: '3c134598-eecb-42ab-9ad3-2b0873f771b2', }), openSystemPreferencesAction: action('open-system-preferences-action'), setGroupCallVideoRequest: action('set-group-call-video-request'), setLocalAudio: action('set-local-audio'), setLocalPreview: action('set-local-preview'), setLocalVideo: action('set-local-video'), setPresenting: action('toggle-presenting'), setRendererCanvas: action('set-renderer-canvas'), stickyControls: boolean('stickyControls', false), toggleParticipants: action('toggle-participants'), togglePip: action('toggle-pip'), toggleScreenRecordingPermissionsDialog: action( 'toggle-screen-recording-permissions-dialog' ), 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, presenting: false, sharingScreen: false, videoAspectRatio: 1.3, ...getDefaultConversationWithUuid({ isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1, title: `Participant ${index + 1}`, }), })); story.add('Group call - Many', () => { return ( ); }); story.add('Group call - reconnecting', () => ( )); story.add('Group call - 0', () => ( )); story.add('Group call - someone is sharing screen', () => ( ({ ...participant, presenting: index === 1, sharingScreen: index === 1, })), })} /> )); story.add( "Group call - someone is sharing screen and you're reconnecting", () => ( ({ ...participant, presenting: index === 1, sharingScreen: index === 1, })), })} /> ) );