packages/storybook8/stories/controlsUtils.ts (580 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { ErrorType, NotificationType } from '@azure/communication-react'; import { PartialTheme } from '@fluentui/react'; import { DefaultTheme, DarkTheme, TeamsTheme, WordTheme } from '@fluentui/theme-samples'; import { mediaGalleryWidthDefault, mediaGalleryWidthOptions, mediaGalleryHeightDefault, mediaGalleryHeightOptions } from './constants'; const remoteParticipantAvatars = ['Default', 'Cat', 'Fox', 'Koala']; export const getControlledRemoteParticipantAvatarSymbol = (AvatarName: string): string => { switch (AvatarName) { case 'Default': return '🤖'; case 'Cat': return '🐱'; case 'Fox': return '🦊'; case 'Koala': return '🐨'; } return '🤖'; }; const CONTROL_BAR_LAYOUTS = [ 'horizontal', 'vertical', 'dockedTop', 'dockedBottom', 'dockedLeft', 'dockedRight', 'floatingTop', 'floatingBottom', 'floatingLeft', 'floatingRight' ] as const; export const defaultControlsCameras: { id: string; name: string }[] = [ { id: 'camera1', name: 'Full HD Webcam' }, { id: 'camera2', name: 'Macbook Pro Webcam' } ]; export const defaultControlsMicrophones: { id: string; name: string }[] = [ { id: 'mic1', name: 'Realtek HD Audio' }, { id: 'mic2', name: 'Macbook Pro Mic' } ]; export const defaultControlsSpeakers: { id: string; name: string }[] = [ { id: 'speaker1', name: 'Realtek HD Audio' }, { id: 'speaker2', name: 'Macbook Pro Speaker' } ]; const defaultControlsGridParticipants = [ { displayName: 'Michael', isVideoReady: false }, { displayName: 'Jim', isVideoReady: false }, { displayName: 'Pam', isVideoReady: false }, { displayName: 'Dwight', isVideoReady: false } ]; export const defaultLocalParticipant = [{ name: 'You', status: 'Connected', isMuted: false, isScreenSharing: false }]; export const defaultRemoteParticipants = [ { name: 'Rick', status: 'InLobby', isMuted: false, isScreenSharing: false }, { name: 'Daryl', status: 'Connecting', isMuted: false, isScreenSharing: false }, { name: 'Michonne', status: 'Idle', isMuted: false, isScreenSharing: false } ]; export const defaultTypingUsers = [ { userId: '1', displayName: 'User1' }, { userId: '2', displayName: 'User2' } ]; const defaultIncomingCallNotifications = [ { callerInfo: { displayName: 'John Wick' }, id: '1' }, { callerInfo: { displayName: 'Dog' }, id: '2' }, { callerInfo: { displayName: 'Dog2' }, id: '3' } ]; const errorOptions: ErrorType[] = [ 'unableToReachChatService', 'accessDenied', 'userNotInChatThread', 'sendMessageNotInChatThread', 'sendMessageGeneric', 'startVideoGeneric', 'stopVideoGeneric', 'muteGeneric', 'unmuteGeneric', 'startScreenShareGeneric', 'stopScreenShareGeneric', 'callNetworkQualityLow', 'callNoSpeakerFound', 'callNoMicrophoneFound', 'callMicrophoneAccessDenied', 'callMicrophoneMutedBySystem', 'callMicrophoneUnmutedBySystem', 'callMacOsMicrophoneAccessDenied', 'callLocalVideoFreeze', 'callCameraAlreadyInUse', 'callMacOsCameraAccessDenied', 'callMacOsScreenShareAccessDenied', 'callVideoStoppedBySystem', 'callVideoRecoveredBySystem' ]; const themeChoices = ['Default', 'Dark', 'Teams', 'Word']; export const getControlledTheme = (choice: string): PartialTheme => { switch (choice) { case 'Default': return DefaultTheme; case 'Dark': return DarkTheme; case 'Teams': return TeamsTheme; case 'Word': return WordTheme; } return DefaultTheme; }; const VIDEO_GALLERY_LAYOUTS = ['default', 'floatingLocalVideo', 'speaker', 'focusedContent'] as const; const OVERFLOW_GALLERY_LAYOUTS = ['horizontalBottom', 'verticalRight', 'horizontalTop'] as const; export const orientationArg = { options: ['landscape', 'portrait'], control: { type: 'radio' }, defaultValue: 'landscape' }; export const controlsForImageOverlay = { showTitle: { control: 'text', name: 'Set Title' }, setAltText: { control: 'text', name: 'Set Alt Text' } }; const notificationOptions: NotificationType[] = [ 'startVideoGeneric', 'stopVideoGeneric', 'muteGeneric', 'unmuteGeneric', 'speakingWhileMuted', 'startScreenShareGeneric', 'stopScreenShareGeneric', 'callNetworkQualityLow', 'teamsMeetingCallNetworkQualityLow', 'callNoSpeakerFound', 'callNoMicrophoneFound', 'callMicrophoneAccessDenied', 'callMicrophoneAccessDeniedSafari', 'callMicrophoneMutedBySystem', 'callMicrophoneUnmutedBySystem', 'callMacOsMicrophoneAccessDenied', 'callLocalVideoFreeze', 'callCameraAccessDenied', 'callCameraAccessDeniedSafari', 'callCameraAlreadyInUse', 'callVideoStoppedBySystem', 'callVideoRecoveredBySystem', 'callMacOsCameraAccessDenied', 'callMacOsScreenShareAccessDenied', 'failedToJoinCallGeneric', 'failedToJoinCallInvalidMeetingLink', 'cameraFrozenForRemoteParticipants', 'unableToStartVideoEffect', 'startSpotlightWhileMaxParticipantsAreSpotlighted', 'mutedByRemoteParticipant', 'recordingStarted', 'transcriptionStarted', 'recordingStopped', 'transcriptionStopped', 'recordingAndTranscriptionStarted', 'recordingAndTranscriptionStopped', 'recordingStoppedStillTranscribing', 'transcriptionStoppedStillRecording', 'capabilityTurnVideoOnAbsent', 'capabilityTurnVideoOnPresent', 'capabilityUnmuteMicAbsent', 'capabilityUnmuteMicPresent', 'togetherModeStarted', 'togetherModeEnded', 'transcriptionError', 'transcriptionStartedByYou' ]; export const defaultActiveNotifications = ['callNoSpeakerFound']; export const controlsToAdd = { alternateCallerId: { control: 'text', description: 'added', name: 'Alternate CallerID', type: { name: 'string', required: true } }, appName: { control: 'text', defaultValue: 'Storybook', name: 'App Name' }, avatarInitials: { control: 'text', defaultValue: 'A B', name: 'Avatar initials' }, remoteParticipantAvatar: { control: 'radio', options: remoteParticipantAvatars, defaultValue: 'Default', name: 'Remote Participant Avatar' }, remoteParticipantChatTopic: { control: 'text', name: 'Chat Topic', type: { name: 'string', required: true } }, remoteParticipantToken: { control: 'text', defaultValue: '', name: 'Valid token for a remote participant', type: { name: 'string', required: true } }, remoteParticipantUserId: { control: 'text', defaultValue: '', name: 'User identifier for a remote participant', type: { name: 'string', required: true } }, calleeUserId: { control: 'text', defaultValue: '8:echo123', name: "Callee's User identifier", type: { name: 'string', required: true } }, calleeToken: { control: 'text', defaultValue: '', name: "Callee's Valid token" }, callerImages: { control: 'file', accept: '.jpeg, .jpg, .png', defaultValue: [], name: 'Avatar' }, callerName: { control: 'text', defaultValue: 'Maximus Aurelius', name: 'Caller Name' }, callerNameAlt: { control: 'text', defaultValue: '1st', name: 'Caller Name Alt' }, callerTitle: { control: 'text', defaultValue: 'Emperor and Philosopher, Rome', name: 'Caller Title' }, callInvitationURL: { control: 'text', defaultValue: '', name: 'URL to invite other participants to the call' }, callLocator: { control: 'text', defaultValue: '', name: 'Call locator (ACS group ID, Teams meeting link, or Room ID)' }, targetParticipantsPSTN: { control: 'text', defaultValue: '', name: 'Phone number(s) to call (comma separated)' }, callModalAlertText: { control: 'text', defaultValue: 'Incoming Video Call', name: 'Alert Text' }, callToastAlertText: { control: 'text', defaultValue: 'Incoming Call', name: 'Alert Text' }, callStateText: { control: 'text', defaultValue: "You're in the lobby", name: 'Call State Text' }, callStateSubText: { control: 'text', defaultValue: 'You should be admitted shortly', name: 'Call State Subtext' }, cameras: { control: 'object', defaultValue: defaultControlsCameras, name: 'Cameras' }, chatThreadId: { control: 'text', defaultValue: '', name: 'Existing thread', type: { name: 'string', required: true } }, checked: { control: 'boolean', defaultValue: false, name: 'Is checked' }, controlBarDefaultIcons: { control: 'radio', options: ['airplane', 'bus', 'ship'], defaultValue: 'airplane', name: 'Icon' }, controlBarLayout: { control: 'select', options: CONTROL_BAR_LAYOUTS, defaultValue: 'floatingBottom', name: 'Layout' }, siteDeviceRequest: { control: 'select', options: ['Camera and Microphone', 'Camera Only', 'Microphone Only'], defaultValue: 'Camera and Microphone', name: 'Device Request Type' }, siteDeviceRequestStatus: { control: 'select', options: ['Request', 'Denied', 'Check'], defaultValue: 'Request', name: 'Request Status' }, disabled: { control: 'boolean', defaultValue: false, name: 'Disable component' }, displayName: { control: 'text', name: 'Display Name' }, enableJumpToNewMessageButton: { control: 'boolean', name: 'Enable Jump To New Message' }, endpointUrl: { control: 'text', defaultValue: '', name: 'Azure Communication Services endpoint', type: { name: 'string', required: true } }, errorTypes: { control: 'check', options: errorOptions, defaultValue: ['accessDenied'], name: 'ErrorType' }, excludeMeFromList: { control: 'boolean', defaultValue: false, name: 'Are you excluded from the list' }, font: { control: 'text', defaultValue: 'Monaco, Menlo, Consolas', name: 'Font' }, gridParticipants: { control: 'object', defaultValue: defaultControlsGridParticipants, name: 'Participants' }, isCameraEnabled: { control: 'boolean', defaultValue: true, name: 'Is camera available' }, isMe: { control: 'boolean', defaultValue: false, name: 'Is You' }, isMicrophoneEnabled: { control: 'boolean', defaultValue: true, name: 'Is microphone available' }, isMuteAllAvailable: { control: 'boolean', defaultValue: false, name: 'Mute all participants option' }, isMuted: { control: 'boolean', defaultValue: false, name: 'Is muted' }, isSpeaking: { control: 'boolean', name: 'Is Speaking' }, isScreenSharing: { control: 'boolean', defaultValue: false, name: 'Is screen sharing' }, isRaisedHand: { control: 'boolean', defaultValue: false, name: 'Is Raised Hand' }, isSendBoxWithWarning: { control: 'boolean', name: 'Has warning/information message' }, isSendBoxWithAttachments: { control: 'boolean', name: 'Has attachments' }, isVideoAvailable: { control: 'boolean', defaultValue: true, name: 'Is video available' }, isVideoMirrored: { control: 'boolean', name: 'Is video mirrored' }, isVideoReady: { control: 'boolean', name: 'Is video ready' }, layoutHeight: { control: { type: 'range', min: mediaGalleryHeightOptions.min, max: mediaGalleryHeightOptions.max, step: mediaGalleryHeightOptions.step }, defaultValue: mediaGalleryHeightDefault, name: 'Height (px)' }, layoutWidth: { control: { type: 'range', min: mediaGalleryWidthOptions.min, max: mediaGalleryWidthOptions.max, step: mediaGalleryWidthOptions.step }, defaultValue: mediaGalleryWidthDefault, name: 'Width (px)' }, localParticipantDisplayName: { control: 'text', defaultValue: 'John Doe', name: 'Local Participant displayName' }, localParticipant: { control: 'object', name: 'Your information' }, localVideoInverted: { control: 'boolean', defaultValue: true, name: 'Invert Local Video' }, localVideoStreamEnabled: { control: 'boolean', defaultValue: true, name: 'Turn Local Video On' }, messageDeliveredTooltipText: { control: 'text', defaultValue: 'Sent', name: 'Delivered icon tooltip text' }, messageFailedToSendTooltipText: { control: 'text', defaultValue: 'Failed to send', name: 'Failed to send icon tooltip text' }, messageSeenTooltipText: { control: 'text', defaultValue: 'Seen', name: 'Seen icon tooltip text' }, messageReadByTooltipText: { control: 'text', defaultValue: 'Read by 2 of 2', name: 'Read by icon tooltip text' }, messageSendingTooltipText: { control: 'text', defaultValue: 'Sending', name: 'Sending icon tooltip text' }, messageStatus: { control: 'select', options: ['delivered', 'sending', 'seen', 'failed'], defaultValue: 'delivered', name: 'Message Status' }, microphones: { control: 'object', defaultValue: defaultControlsMicrophones, name: 'Microphones' }, formFactor: { control: 'select', options: ['desktop', 'mobile'], defaultValue: 'desktop', name: 'Form factor' }, participantItemMenuItemsStr: { control: 'text', defaultValue: 'Mute, Remove', name: 'Menu items (comma separated)' }, participantNames: { control: 'text', defaultValue: 'You, Hal Jordan, Barry Allen, Bruce Wayne', name: 'Participants (comma separated with You being local user)' }, remoteParticipantNames: { control: 'text', defaultValue: 'Rick, Daryl, Michonne, Dwight, Pam, Michael, Jim, Kevin, Creed, Angela, Andy, Stanley, Meredith, Phyllis, Oscar, Ryan, Kelly, Andy, Toby, Darryl, Gabe, Erin', name: 'Remote participants (comma separated)' }, remoteParticipants: { control: 'object', name: 'Remote participants' }, requiredDisplayName: { control: 'text', defaultValue: 'John Smith', name: 'Display name', type: { required: true, name: 'string' } }, screenShareExperience: { control: 'select', options: ['none', 'presenter', 'viewer'], defaultValue: 'none', name: 'Screen sharing experience' }, sendBoxWarningMessage: { control: 'text', name: 'Warning/information message for SendBox' }, showChatParticipants: { control: 'boolean', defaultValue: true, name: 'Show Participants Pane' }, showChatTopic: { control: 'boolean', defaultValue: true, name: 'Show Topic' }, showErrorBar: { control: 'boolean', defaultValue: true, name: 'Show ErrorBar' }, showLabel: { control: 'boolean', defaultValue: false, name: 'Show label' }, showVideoTileLabel: { control: 'boolean', name: 'Show label' }, showMessageDate: { control: 'boolean', name: 'Enable Message Date' }, showMessageStatus: { control: 'boolean', name: 'Enable Message Status Indicator' }, showMuteIndicator: { control: 'boolean', name: 'Show Mute/UnMute Indicator' }, speakers: { control: 'object', defaultValue: defaultControlsSpeakers, name: 'Speakers' }, teamsMeetingLink: { control: 'text', defaultValue: '', name: 'Teams meeting link' }, theme: { control: 'radio', options: themeChoices, defaultValue: 'Default', name: 'Theme' }, token: { control: 'text', defaultValue: '', name: 'Valid token for user', type: { name: 'string', required: true } }, typingUsers: { control: 'object', name: 'Typing users' }, isCaptionsFeatureActive: { control: 'boolean', defaultValue: true, name: 'Is captions on' }, userId: { control: 'text', defaultValue: '', name: 'User identifier for user', type: { name: 'string', required: true }, value: '' }, videoGallerylayout: { control: 'select', options: VIDEO_GALLERY_LAYOUTS, defaultValue: 'floatingLocalVideo', name: 'VideoGallery Layout' }, overflowGalleryPosition: { control: 'select', options: OVERFLOW_GALLERY_LAYOUTS, defaultValue: 'horizontalBottom', name: 'Overflow Gallery Position' }, localVideoTileSize: { control: 'select', options: ['9:16', '16:9', 'hidden', 'followDeviceOrientation'], defaultValue: 'followDeviceOrientation', name: 'Local Video Tile Size' }, videoTileHeight: { control: { type: 'range', min: 80, max: 800, step: 10 }, name: 'Height (px)' }, videoTileWidth: { control: { type: 'range', min: 100, max: 1200, step: 10 }, name: 'Width (px)' }, callWithChatControlOptions: { control: 'object', defaultValue: { microphoneButton: true, cameraButton: true, screenShareButton: true, devicesButton: true, peopleButton: true, chatButton: true, displayType: 'default' }, name: 'Control Bar Customizations' }, customButtonInjectionControls: { placement: { control: 'select', if: { arg: 'allowRawObjectInput', truthy: false }, options: ['primary', 'secondary', 'overflow'], defaultValue: 'primary', name: 'Placement' }, disabled: { control: 'boolean', if: { arg: 'allowRawObjectInput', truthy: false }, defaultValue: 'false', name: 'Disabled' }, label: { control: 'text', if: { arg: 'allowRawObjectInput', truthy: false }, defaultValue: 'Custom', name: 'Button label' }, icon: { control: 'text', if: { arg: 'allowRawObjectInput', truthy: false }, defaultValue: 'DefaultCustomButton', name: 'Button icon' }, showLabel: { control: { type: 'radio' }, if: { arg: 'allowRawObjectInput', truthy: false }, defaultValue: 'undefined', options: ['undefined', false, true], name: 'Show Label' }, // Object injection is converting function consts to string. This is causing errors with this control. // allowRawObjectInput: { // control: 'boolean', // defaultValue: false, // if: { arg: 'injectMaximumNumberOfButtons', truthy: false }, // name: 'Inject your own buttons' // }, // objectOptions: { // control: 'object', // if: { arg: 'allowRawObjectInput' }, // defaultValue: [ // (): CustomCallControlButtonProps => ({ // placement: 'primary', // strings: { // label: 'Custom' // } // }), // (): CustomCallControlButtonProps => ({ // placement: 'secondary', // strings: { // label: 'Custom' // } // }), // (): CustomCallControlButtonProps => ({ // placement: 'overflow', // strings: { // label: 'Custom' // } // }) // ] // }, injectMaximumNumberOfButtons: { control: 'boolean', defaultValue: false, if: { arg: 'allowRawObjectInput', truthy: false }, name: 'Inject Max # of Custom Buttons' } }, isNotificationAutoDismiss: { control: 'boolean', defaultValue: false, name: 'Is auto dismiss on' }, showNotificationStacked: { control: 'boolean', defaultValue: false, name: 'Show notification stacked effect' }, incomingCalls: { control: 'object', defaultValue: defaultIncomingCallNotifications, name: 'Incoming Calls' }, maxIncomingCallsToShow: { control: 'select', options: [1, 2, 3], defaultValue: '2', name: 'Number of incoming calls' }, activeNotifications: { control: 'check', options: notificationOptions, name: 'Active notifications to show' }, maxNotificationsToShow: { control: 'select', options: [1, 2, 3], defaultValue: '2', name: 'Select max number of notifications to show' }, richTextEditor: { control: 'boolean', name: 'Enable rich text editor' }, isRTTTyping: { control: 'boolean', name: 'Is RTT typing' }, rttDisplayName: { control: 'text', name: 'RTT display name' }, rttCaptionText: { control: 'text', name: 'RTT caption text' }, isCaptionsOn: { control: 'boolean', name: 'Is captions on' }, isRealTimeTextOn: { control: 'boolean', name: 'Is real time text on' }, startCaptionsInProgress: { control: 'boolean', name: 'Start captions in progress' }, captionsFormFactor: { control: 'radio', options: ['default', 'compact'], defaultValue: 'default', name: 'Form factor' } }; export const hiddenControl = { control: false, table: { disable: true } }; export const defaultCallCompositeHiddenControls = { adapter: hiddenControl, fluentTheme: hiddenControl, onRenderAvatar: hiddenControl, identifiers: hiddenControl, locale: hiddenControl, onFetchAvatarPersonaData: hiddenControl, rtl: hiddenControl, options: hiddenControl, callInvitationUrl: hiddenControl, formFactor: hiddenControl, // formFactor is hidden by default and compositeFormFactor is used as a prop instead to workaround a bug where formFactor is not put in the correct order when the controls are generated role: hiddenControl, // TODO: once role work is complete this should be added as a drop down control icons: hiddenControl, onFetchParticipantMenuItems: hiddenControl }; export const defaultChatCompositeHiddenControls = { adapter: hiddenControl, fluentTheme: hiddenControl, onRenderAvatar: hiddenControl, onRenderMessage: hiddenControl, onRenderTypingIndicator: hiddenControl, options: hiddenControl, identifiers: hiddenControl, locale: hiddenControl, onFetchAvatarPersonaData: hiddenControl, onFetchParticipantMenuItems: hiddenControl, rtl: hiddenControl, formFactor: hiddenControl // formFactor is hidden by default and compositeFormFactor is used as a prop instead to workaround a bug where formFactor is not put in the correct order when the controls are generated }; export const defaultCallWithChatCompositeHiddenControls = { adapter: hiddenControl, fluentTheme: hiddenControl, joinInvitationURL: hiddenControl, rtl: hiddenControl, options: hiddenControl, locale: hiddenControl, icons: hiddenControl, onFetchAvatarPersonaData: hiddenControl, onFetchParticipantMenuItems: hiddenControl, formFactor: hiddenControl // formFactor is hidden by default and compositeFormFactor is used as a prop instead to workaround a bug where formFactor is not put in the correct order when the controls are generated }; /** * Helper function to get strongly typed storybook args. * * @remarks * This only extracts the keys of the storybook control args and not the type of each control. * This is because we cannot type infer between the storybook control and its output. * Instead, for ease of use, we use `any`. */ export type ArgsFrom<TControlArgs> = Record<keyof TControlArgs, any>;