signal-desktop/ts/components/CallScreen.stories.tsx

871 lines
23 KiB
TypeScript
Raw Normal View History

2023-01-03 19:55:46 +00:00
// Copyright 2020 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
2020-06-04 18:16:19 +00:00
import * as React from 'react';
2023-12-13 17:49:30 +00:00
import { sample, shuffle, times } from 'lodash';
2020-09-12 00:46:52 +00:00
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type {
2023-11-16 19:55:35 +00:00
ActiveCallReactionsType,
ActiveGroupCallType,
GroupCallRemoteParticipantType,
} from '../types/Calling';
import {
CallMode,
CallViewMode,
CallState,
GroupCallConnectionState,
GroupCallJoinState,
} from '../types/Calling';
import { generateAci } from '../types/ServiceId';
import type { ConversationType } from '../state/ducks/conversations';
2021-05-28 16:15:17 +00:00
import { AvatarColors } from '../types/Colors';
import type { PropsType } from './CallScreen';
import { CallScreen as UnwrappedCallScreen } from './CallScreen';
import { DEFAULT_PREFERRED_REACTION_EMOJI } from '../reactions/constants';
2021-09-18 00:30:08 +00:00
import { setupI18n } from '../util/setupI18n';
2020-12-02 18:14:03 +00:00
import { missingCaseError } from '../util/missingCaseError';
2021-10-26 22:59:08 +00:00
import {
getDefaultConversation,
2023-08-16 20:54:39 +00:00
getDefaultConversationWithServiceId,
2021-10-26 22:59:08 +00:00
} from '../test-both/helpers/getDefaultConversation';
2021-01-08 18:58:28 +00:00
import { fakeGetGroupCallVideoFrameSource } from '../test-both/helpers/fakeGetGroupCallVideoFrameSource';
2020-06-04 18:16:19 +00:00
import enMessages from '../../_locales/en/messages.json';
import { CallingToastProvider, useCallingToasts } from './CallingToast';
import type { CallingImageDataCache } from './CallManager';
2020-06-04 18:16:19 +00:00
const MAX_PARTICIPANTS = 75;
2023-12-06 21:52:29 +00:00
const LOCAL_DEMUX_ID = 1;
2021-01-08 18:58:28 +00:00
2020-06-04 18:16:19 +00:00
const i18n = setupI18n('en', enMessages);
2021-05-07 22:21:10 +00:00
const conversation = getDefaultConversation({
2020-12-02 18:14:03 +00:00
id: '3051234567',
avatarPath: undefined,
2021-05-28 16:15:17 +00:00
color: AvatarColors[0],
2020-12-02 18:14:03 +00:00
title: 'Rick Sanchez',
name: 'Rick Sanchez',
phoneNumber: '3051234567',
profileName: 'Rick Sanchez',
2021-05-07 22:21:10 +00:00
});
2020-12-02 18:14:03 +00:00
type OverridePropsBase = {
2020-12-02 18:14:03 +00:00
hasLocalAudio?: boolean;
hasLocalVideo?: boolean;
2022-05-19 03:28:51 +00:00
localAudioLevel?: number;
viewMode?: CallViewMode;
2023-11-16 19:55:35 +00:00
reactions?: ActiveCallReactionsType;
};
2020-11-17 15:07:53 +00:00
type DirectCallOverrideProps = OverridePropsBase & {
2020-12-02 18:14:03 +00:00
callMode: CallMode.Direct;
callState?: CallState;
hasRemoteVideo?: boolean;
};
2020-12-02 18:14:03 +00:00
type GroupCallOverrideProps = OverridePropsBase & {
2020-12-02 18:14:03 +00:00
callMode: CallMode.Group;
connectionState?: GroupCallConnectionState;
peekedParticipants?: Array<ConversationType>;
pendingParticipants?: Array<ConversationType>;
2023-12-06 21:52:29 +00:00
raisedHands?: Set<number>;
2020-12-02 18:14:03 +00:00
remoteParticipants?: Array<GroupCallRemoteParticipantType>;
remoteAudioLevel?: number;
};
2020-12-02 18:14:03 +00:00
const createActiveDirectCallProp = (
overrideProps: DirectCallOverrideProps
) => ({
callMode: CallMode.Direct as CallMode.Direct,
conversation,
callState: overrideProps.callState ?? CallState.Accepted,
2020-12-02 18:14:03 +00:00
peekedParticipants: [] as [],
remoteParticipants: [
{
hasRemoteVideo: overrideProps.hasRemoteVideo ?? false,
presenting: false,
title: 'test',
2020-12-02 18:14:03 +00:00
},
] as [
{
hasRemoteVideo: boolean;
presenting: boolean;
title: string;
2020-12-02 18:14:03 +00:00
}
],
});
2023-12-06 21:52:29 +00:00
const getConversationsByDemuxId = (overrideProps: GroupCallOverrideProps) => {
const conversationsByDemuxId = new Map<number, ConversationType>(
2023-11-16 19:55:35 +00:00
overrideProps.remoteParticipants?.map((participant, index) => [
participant.demuxId,
getDefaultConversationWithServiceId({
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
title: `Participant ${index + 1}`,
}),
])
2023-12-06 21:52:29 +00:00
);
conversationsByDemuxId.set(LOCAL_DEMUX_ID, conversation);
return conversationsByDemuxId;
};
const getRaisedHands = (overrideProps: GroupCallOverrideProps) => {
if (!overrideProps.remoteParticipants) {
return;
}
return new Set<number>(
overrideProps.remoteParticipants
.filter(participant => participant.isHandRaised)
.map(participant => participant.demuxId)
);
};
2023-12-06 21:52:29 +00:00
const createActiveGroupCallProp = (overrideProps: GroupCallOverrideProps) => ({
callMode: CallMode.Group as CallMode.Group,
connectionState:
overrideProps.connectionState || GroupCallConnectionState.Connected,
conversationsByDemuxId: getConversationsByDemuxId(overrideProps),
2020-12-02 18:14:03 +00:00
joinState: GroupCallJoinState.Joined,
2023-12-06 21:52:29 +00:00
localDemuxId: LOCAL_DEMUX_ID,
2020-12-02 18:14:03 +00:00
maxDevices: 5,
deviceCount: (overrideProps.remoteParticipants || []).length,
groupMembers: overrideProps.remoteParticipants || [],
2020-12-02 18:14:03 +00:00
// Because remote participants are a superset, we can use them in place of peeked
// participants.
isConversationTooBigToRing: false,
2020-12-02 18:14:03 +00:00
peekedParticipants:
overrideProps.peekedParticipants || overrideProps.remoteParticipants || [],
pendingParticipants: overrideProps.pendingParticipants || [],
raisedHands:
overrideProps.raisedHands ||
getRaisedHands(overrideProps) ||
new Set<number>(),
2020-12-02 18:14:03 +00:00
remoteParticipants: overrideProps.remoteParticipants || [],
remoteAudioLevels: new Map<number, number>(
overrideProps.remoteParticipants?.map((_participant, index) => [
index,
overrideProps.remoteAudioLevel ?? 0,
])
),
2023-11-16 19:55:35 +00:00
reactions: overrideProps.reactions || [],
2020-12-02 18:14:03 +00:00
});
const createActiveCallProp = (
overrideProps: DirectCallOverrideProps | GroupCallOverrideProps
) => {
const baseResult = {
joinedAt: Date.now(),
conversation,
hasLocalAudio: overrideProps.hasLocalAudio ?? false,
hasLocalVideo: overrideProps.hasLocalVideo ?? false,
localAudioLevel: overrideProps.localAudioLevel ?? 0,
viewMode: overrideProps.viewMode ?? CallViewMode.Overflow,
outgoingRing: true,
2020-12-02 18:14:03 +00:00
pip: false,
settingsDialogOpen: false,
showParticipantsList: false,
2020-11-17 15:07:53 +00:00
};
2020-12-02 18:14:03 +00:00
switch (overrideProps.callMode) {
case CallMode.Direct:
return { ...baseResult, ...createActiveDirectCallProp(overrideProps) };
case CallMode.Group:
return { ...baseResult, ...createActiveGroupCallProp(overrideProps) };
default:
throw missingCaseError(overrideProps);
}
};
2020-11-17 15:07:53 +00:00
const createProps = (
2020-12-02 18:14:03 +00:00
overrideProps: DirectCallOverrideProps | GroupCallOverrideProps = {
callMode: CallMode.Direct as CallMode.Direct,
}
2020-11-17 15:07:53 +00:00
): PropsType => ({
2020-12-02 18:14:03 +00:00
activeCall: createActiveCallProp(overrideProps),
approveUser: action('approve-user'),
changeCallView: action('change-call-view'),
denyUser: action('deny-user'),
2021-01-08 18:58:28 +00:00
getGroupCallVideoFrameSource: fakeGetGroupCallVideoFrameSource,
getPresentingSources: action('get-presenting-sources'),
hangUpActiveCall: action('hang-up'),
2020-06-04 18:16:19 +00:00
i18n,
imageDataCache: React.createRef<CallingImageDataCache>(),
isCallLinkAdmin: true,
2024-06-11 23:45:28 +00:00
isGroupCallRaiseHandEnabled: true,
me: getDefaultConversation({
2021-05-28 16:15:17 +00:00
color: AvatarColors[1],
id: '6146087e-f7ef-457e-9a8d-47df1fdd6b25',
name: 'Morty Smith',
profileName: 'Morty Smith',
title: 'Morty Smith',
2023-08-16 20:54:39 +00:00
serviceId: generateAci(),
}),
openSystemPreferencesAction: action('open-system-preferences-action'),
2023-11-16 19:55:35 +00:00
renderEmojiPicker: () => <>EmojiPicker</>,
renderReactionPicker: () => <div />,
2023-12-06 21:52:29 +00:00
sendGroupCallRaiseHand: action('send-group-call-raise-hand'),
2023-11-16 19:55:35 +00:00
sendGroupCallReaction: action('send-group-call-reaction'),
setGroupCallVideoRequest: action('set-group-call-video-request'),
2020-06-04 18:16:19 +00:00
setLocalAudio: action('set-local-audio'),
2020-08-27 00:03:42 +00:00
setLocalPreview: action('set-local-preview'),
2020-06-04 18:16:19 +00:00
setLocalVideo: action('set-local-video'),
setPresenting: action('toggle-presenting'),
2020-08-27 00:03:42 +00:00
setRendererCanvas: action('set-renderer-canvas'),
stickyControls: false,
switchToPresentationView: action('switch-to-presentation-view'),
switchFromPresentationView: action('switch-from-presentation-view'),
2020-11-17 15:07:53 +00:00
toggleParticipants: action('toggle-participants'),
2020-10-01 00:43:05 +00:00
togglePip: action('toggle-pip'),
toggleScreenRecordingPermissionsDialog: action(
'toggle-screen-recording-permissions-dialog'
),
2020-08-27 00:03:42 +00:00
toggleSettings: action('toggle-settings'),
2020-10-08 01:25:33 +00:00
});
function CallScreen(props: ReturnType<typeof createProps>): JSX.Element {
return (
<CallingToastProvider i18n={i18n}>
<UnwrappedCallScreen {...props} />
</CallingToastProvider>
);
}
2022-06-07 00:48:02 +00:00
export default {
title: 'Components/CallScreen',
argTypes: {},
args: {},
excludeStories: ['allRemoteParticipants'],
} satisfies Meta<PropsType>;
2020-10-08 01:25:33 +00:00
2022-11-18 00:45:19 +00:00
export function Default(): JSX.Element {
2020-10-08 01:25:33 +00:00
return <CallScreen {...createProps()} />;
2022-11-18 00:45:19 +00:00
}
2020-10-08 01:25:33 +00:00
2022-11-18 00:45:19 +00:00
export function PreRing(): JSX.Element {
2020-11-17 15:07:53 +00:00
return (
<CallScreen
{...createProps({
2020-12-02 18:14:03 +00:00
callMode: CallMode.Direct,
2020-11-17 15:07:53 +00:00
callState: CallState.Prering,
})}
/>
);
2022-11-18 00:45:19 +00:00
}
2022-06-07 00:48:02 +00:00
export function Ringing(): JSX.Element {
2020-11-17 15:07:53 +00:00
return (
<CallScreen
{...createProps({
2020-12-02 18:14:03 +00:00
callMode: CallMode.Direct,
2020-11-17 15:07:53 +00:00
callState: CallState.Ringing,
})}
/>
);
}
2020-10-08 01:25:33 +00:00
export function Reconnecting(): JSX.Element {
2020-11-17 15:07:53 +00:00
return (
<CallScreen
{...createProps({
2020-12-02 18:14:03 +00:00
callMode: CallMode.Direct,
2020-11-17 15:07:53 +00:00
callState: CallState.Reconnecting,
})}
/>
);
}
2020-10-08 01:25:33 +00:00
export function Ended(): JSX.Element {
2020-11-17 15:07:53 +00:00
return (
<CallScreen
{...createProps({
2020-12-02 18:14:03 +00:00
callMode: CallMode.Direct,
2020-11-17 15:07:53 +00:00
callState: CallState.Ended,
})}
/>
);
}
2020-06-04 18:16:19 +00:00
2022-11-18 00:45:19 +00:00
export function HasLocalAudio(): JSX.Element {
2020-12-02 18:14:03 +00:00
return (
<CallScreen
{...createProps({
callMode: CallMode.Direct,
hasLocalAudio: true,
})}
/>
);
2022-11-18 00:45:19 +00:00
}
2020-06-04 18:16:19 +00:00
2022-11-18 00:45:19 +00:00
export function HasLocalVideo(): JSX.Element {
2020-12-02 18:14:03 +00:00
return (
<CallScreen
{...createProps({
callMode: CallMode.Direct,
hasLocalVideo: true,
})}
/>
);
2022-11-18 00:45:19 +00:00
}
2022-06-07 00:48:02 +00:00
2022-11-18 00:45:19 +00:00
export function HasRemoteVideo(): JSX.Element {
2020-12-02 18:14:03 +00:00
return (
<CallScreen
{...createProps({
callMode: CallMode.Direct,
hasRemoteVideo: true,
})}
/>
);
2022-11-18 00:45:19 +00:00
}
2020-11-17 15:07:53 +00:00
2022-11-18 00:45:19 +00:00
export function GroupCall1(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [
{
aci: generateAci(),
2022-11-18 00:45:19 +00:00
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
2023-12-06 21:52:29 +00:00
isHandRaised: false,
2024-01-23 19:08:21 +00:00
mediaKeysReceived: true,
2023-12-06 21:52:29 +00:00
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
serviceId: generateAci(),
title: 'Tyler',
}),
},
],
})}
/>
);
}
export function GroupCallYourHandRaised(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [
{
aci: generateAci(),
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
isHandRaised: false,
2024-01-23 19:08:21 +00:00
mediaKeysReceived: true,
2022-11-18 00:45:19 +00:00
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
2023-08-16 20:54:39 +00:00
serviceId: generateAci(),
2022-11-18 00:45:19 +00:00
title: 'Tyler',
}),
},
],
2023-12-06 21:52:29 +00:00
raisedHands: new Set([LOCAL_DEMUX_ID]),
2022-11-18 00:45:19 +00:00
})}
/>
);
}
2022-06-07 00:48:02 +00:00
const PARTICIPANT_EMOJIS = ['❤️', '🤔', '✨', '😂', '🦄'] as const;
2021-01-08 18:58:28 +00:00
// We generate these upfront so that the list is stable when you move the slider.
export const allRemoteParticipants = times(MAX_PARTICIPANTS).map(index => {
2024-01-23 19:08:21 +00:00
const mediaKeysReceived = (index + 1) % 20 !== 0;
return {
aci: generateAci(),
addedTime: Date.now() - 60000,
demuxId: index,
hasRemoteAudio: mediaKeysReceived ? index % 3 !== 0 : false,
hasRemoteVideo: mediaKeysReceived ? index % 4 !== 0 : false,
isHandRaised: (index - 3) % 10 === 0,
mediaKeysReceived,
presenting: false,
sharingScreen: false,
videoAspectRatio: Math.random() < 0.7 ? 1.3 : Math.random() * 0.4 + 0.6,
...getDefaultConversationWithServiceId({
isBlocked: index === 10 || index === MAX_PARTICIPANTS - 1,
title: `Participant ${
(index - 2) % 4 === 0
? PARTICIPANT_EMOJIS[
Math.floor((index - 2) / 4) % PARTICIPANT_EMOJIS.length
]
: ''
} ${index + 1}`,
}),
};
});
2021-01-08 18:58:28 +00:00
export function GroupCallManyPaginated(): JSX.Element {
const props = createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants,
viewMode: CallViewMode.Paginated,
});
return <CallScreen {...props} />;
}
export function GroupCallManyPaginatedEveryoneTalking(): JSX.Element {
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants,
viewMode: CallViewMode.Paginated,
})
);
const activeCall = useMakeEveryoneTalk(
props.activeCall as ActiveGroupCallType
);
return <CallScreen {...props} activeCall={activeCall} />;
}
export function GroupCallManyOverflow(): JSX.Element {
2021-01-08 18:58:28 +00:00
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants,
viewMode: CallViewMode.Overflow,
2021-01-08 18:58:28 +00:00
})}
/>
);
2022-11-18 00:45:19 +00:00
}
2022-06-07 00:48:02 +00:00
export function GroupCallManyOverflowEveryoneTalking(): JSX.Element {
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants,
viewMode: CallViewMode.Overflow,
})
);
const activeCall = useMakeEveryoneTalk(
props.activeCall as ActiveGroupCallType
);
return <CallScreen {...props} activeCall={activeCall} />;
}
2023-10-25 13:40:22 +00:00
export function GroupCallSpeakerView(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
viewMode: CallViewMode.Speaker,
remoteParticipants: allRemoteParticipants.slice(0, 3),
})}
/>
);
}
2022-11-18 00:45:19 +00:00
export function GroupCallReconnecting(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Reconnecting,
remoteParticipants: [
{
aci: generateAci(),
2022-11-18 00:45:19 +00:00
demuxId: 0,
hasRemoteAudio: true,
hasRemoteVideo: true,
2023-12-06 21:52:29 +00:00
isHandRaised: false,
2024-01-23 19:08:21 +00:00
mediaKeysReceived: true,
2022-11-18 00:45:19 +00:00
presenting: false,
sharingScreen: false,
videoAspectRatio: 1.3,
...getDefaultConversation({
isBlocked: false,
title: 'Tyler',
2023-08-16 20:54:39 +00:00
serviceId: generateAci(),
2022-11-18 00:45:19 +00:00
}),
},
],
})}
/>
);
}
2022-06-07 00:48:02 +00:00
2022-11-18 00:45:19 +00:00
export function GroupCall0(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: [],
})}
/>
);
}
2022-06-07 00:48:02 +00:00
2022-11-18 00:45:19 +00:00
export function GroupCallSomeoneIsSharingScreen(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
presenting: index === 1,
sharingScreen: index === 1,
})),
})}
/>
);
}
2022-11-18 00:45:19 +00:00
export function GroupCallSomeoneIsSharingScreenAndYoureReconnecting(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
connectionState: GroupCallConnectionState.Reconnecting,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
presenting: index === 1,
sharingScreen: index === 1,
})),
})}
/>
2022-06-07 00:48:02 +00:00
);
2022-11-18 00:45:19 +00:00
}
export function GroupCallSomeoneStoppedSharingScreen(): JSX.Element {
const [remoteParticipants, setRemoteParticipants] = React.useState(
allRemoteParticipants.slice(0, 5).map((participant, index) => ({
...participant,
presenting: index === 1,
sharingScreen: index === 1,
}))
);
React.useEffect(() => {
setTimeout(
() => setRemoteParticipants(allRemoteParticipants.slice(0, 5)),
1000
);
});
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants,
})}
/>
);
}
function ToastEmitter(): null {
const { showToast } = useCallingToasts();
const toastCount = React.useRef(0);
React.useEffect(() => {
const interval = setInterval(() => {
const autoClose = toastCount.current % 2 === 0;
showToast({
key: Date.now().toString(),
content: `${
autoClose ? 'Disappearing' : 'Non-disappearing'
} toast sent: ${Date.now()}`,
dismissable: true,
autoClose,
});
toastCount.current += 1;
}, 1500);
return () => clearInterval(interval);
}, [showToast]);
return null;
}
export function CallScreenToastAPalooza(): JSX.Element {
return (
<CallingToastProvider i18n={i18n}>
<UnwrappedCallScreen {...createProps()} />
<ToastEmitter />
</CallingToastProvider>
);
}
function useMakeEveryoneTalk(
activeCall: ActiveGroupCallType,
frequency = 2000
) {
const [call, setCall] = React.useState(activeCall);
React.useEffect(() => {
const interval = setInterval(() => {
const idxToStartSpeaking = Math.floor(
Math.random() * call.remoteParticipants.length
);
const demuxIdToStartSpeaking = (
call.remoteParticipants[
idxToStartSpeaking
] as GroupCallRemoteParticipantType
).demuxId;
const remoteAudioLevels = new Map();
for (const [demuxId] of call.remoteAudioLevels.entries()) {
if (demuxId === demuxIdToStartSpeaking) {
remoteAudioLevels.set(demuxId, 1);
} else {
remoteAudioLevels.set(demuxId, 0);
}
}
setCall(state => ({
...state,
remoteParticipants: state.remoteParticipants.map((part, idx) => {
return {
...part,
hasRemoteAudio:
idx === idxToStartSpeaking ? true : part.hasRemoteAudio,
speakerTime:
idx === idxToStartSpeaking
? Date.now()
: (part as GroupCallRemoteParticipantType).speakerTime,
};
}),
remoteAudioLevels,
}));
}, frequency);
return () => clearInterval(interval);
}, [frequency, call]);
return call;
}
2023-11-16 19:55:35 +00:00
export function GroupCallReactions(): JSX.Element {
const remoteParticipants = allRemoteParticipants.slice(0, 5);
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
})
);
const activeCall = useReactionsEmitter({
activeCall: props.activeCall as ActiveGroupCallType,
});
2023-11-16 19:55:35 +00:00
return <CallScreen {...props} activeCall={activeCall} />;
}
export function GroupCallReactionsSpam(): JSX.Element {
2024-01-10 22:35:26 +00:00
const remoteParticipants = allRemoteParticipants.slice(0, 3);
2023-11-16 19:55:35 +00:00
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
})
);
const activeCall = useReactionsEmitter({
activeCall: props.activeCall as ActiveGroupCallType,
frequency: 250,
});
return <CallScreen {...props} activeCall={activeCall} />;
}
export function GroupCallReactionsSkinTones(): JSX.Element {
const remoteParticipants = allRemoteParticipants.slice(0, 3);
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
})
2023-11-16 19:55:35 +00:00
);
const activeCall = useReactionsEmitter({
activeCall: props.activeCall as ActiveGroupCallType,
frequency: 500,
emojis: ['👍', '👍🏻', '👍🏼', '👍🏽', '👍🏾', '👍🏿', '❤️', '😂', '😮', '😢'],
});
2023-11-16 19:55:35 +00:00
return <CallScreen {...props} activeCall={activeCall} />;
}
2024-01-10 22:35:26 +00:00
export function GroupCallReactionsManyInOrder(): JSX.Element {
2023-11-16 19:55:35 +00:00
const timestamp = Date.now();
const remoteParticipants = allRemoteParticipants.slice(0, 5);
const reactions = remoteParticipants.map((participant, i) => {
const { demuxId } = participant;
const value =
DEFAULT_PREFERRED_REACTION_EMOJI[
i % DEFAULT_PREFERRED_REACTION_EMOJI.length
];
2023-11-16 19:55:35 +00:00
return { timestamp, demuxId, value };
});
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
reactions,
})
);
return <CallScreen {...props} />;
}
function useReactionsEmitter({
activeCall,
2023-11-16 19:55:35 +00:00
frequency = 2000,
removeAfter = 5000,
emojis = DEFAULT_PREFERRED_REACTION_EMOJI,
}: {
activeCall: ActiveGroupCallType;
frequency?: number;
removeAfter?: number;
emojis?: Array<string>;
}) {
2023-11-16 19:55:35 +00:00
const [call, setCall] = React.useState(activeCall);
React.useEffect(() => {
const interval = setInterval(() => {
setCall(state => {
const timeNow = Date.now();
const expireAt = timeNow - removeAfter;
const participantIndex = Math.floor(
Math.random() * call.remoteParticipants.length
);
const { demuxId } = call.remoteParticipants[participantIndex];
const reactions: ActiveCallReactionsType = [
...(state.reactions ?? []).filter(
({ timestamp }) => timestamp > expireAt
),
{
timestamp: timeNow,
demuxId,
value: sample(emojis) as string,
2023-11-16 19:55:35 +00:00
},
];
return {
...state,
reactions,
};
});
}, frequency);
return () => clearInterval(interval);
}, [emojis, frequency, removeAfter, call]);
2023-11-16 19:55:35 +00:00
return call;
}
2023-12-13 17:49:30 +00:00
export function GroupCallHandRaising(): JSX.Element {
const remoteParticipants = allRemoteParticipants.slice(0, 10);
const [props] = React.useState(
createProps({
callMode: CallMode.Group,
remoteParticipants,
viewMode: CallViewMode.Overflow,
})
);
const activeCall = useHandRaiser(props.activeCall as ActiveGroupCallType);
return <CallScreen {...props} activeCall={activeCall} />;
}
// Every [frequency] ms, all hands are lowered and [random min to max] random hands
// are raised
function useHandRaiser(
activeCall: ActiveGroupCallType,
frequency = 3000,
min = 0,
max = 5
) {
const [call, setCall] = React.useState(activeCall);
React.useEffect(() => {
const interval = setInterval(() => {
setCall(state => {
const participantsCount = call.remoteParticipants.length;
const usableMax = Math.min(max, participantsCount);
const raiseCount = Math.floor(min + (usableMax - min) * Math.random());
const participantIndices = shuffle(
Array.from(Array(participantsCount).keys())
).slice(0, raiseCount);
const participantIndicesSet = new Set(participantIndices);
const remoteParticipants = [...call.remoteParticipants].map(
(participant, index) => {
return {
...participant,
isHandRaised: participantIndicesSet.has(index),
};
}
);
const raisedHands = new Set(
participantIndices.map(
index => call.remoteParticipants[index].demuxId
)
);
return {
...state,
remoteParticipants,
raisedHands,
};
});
}, frequency);
return () => clearInterval(interval);
}, [frequency, call, max, min]);
return call;
}
2024-01-23 19:08:21 +00:00
export function GroupCallSomeoneMissingMediaKeys(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
addedTime: index === 1 ? Date.now() - 60000 : undefined,
hasRemoteAudio: false,
hasRemoteVideo: false,
mediaKeysReceived: index !== 1,
})),
})}
/>
);
}
export function GroupCallSomeoneBlocked(): JSX.Element {
return (
<CallScreen
{...createProps({
callMode: CallMode.Group,
remoteParticipants: allRemoteParticipants
.slice(0, 5)
.map((participant, index) => ({
...participant,
isBlocked: index === 1,
})),
})}
/>
);
}