@stream-io/video-react-sdk 0.4.26 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +304 -238
- package/README.md +5 -5
- package/dist/css/styles.css +952 -481
- package/dist/css/styles.css.map +1 -1
- package/dist/index.cjs.js +946 -639
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.es.js +939 -639
- package/dist/index.es.js.map +1 -1
- package/dist/src/components/Button/CompositeButton.d.ts +9 -11
- package/dist/src/components/Button/index.d.ts +0 -1
- package/dist/src/components/CallControls/CallStatsButton.d.ts +3 -0
- package/dist/src/components/CallControls/CancelCallButton.d.ts +1 -0
- package/dist/src/components/CallControls/ReactionsButton.d.ts +2 -1
- package/dist/src/components/CallControls/RecordCallButton.d.ts +4 -1
- package/dist/src/components/CallControls/ToggleAudioButton.d.ts +3 -9
- package/dist/src/components/CallControls/ToggleAudioOutputButton.d.ts +2 -5
- package/dist/src/components/CallControls/ToggleVideoButton.d.ts +3 -9
- package/dist/src/components/CallParticipantsList/CallParticipantListHeader.d.ts +3 -1
- package/dist/src/components/CallParticipantsList/CallParticipantListingItem.d.ts +0 -5
- package/dist/src/components/CallStats/CallStats.d.ts +25 -2
- package/dist/src/components/DeviceSettings/DeviceSelector.d.ts +6 -1
- package/dist/src/components/DeviceSettings/DeviceSelectorAudio.d.ts +4 -2
- package/dist/src/components/DeviceSettings/DeviceSelectorVideo.d.ts +2 -1
- package/dist/src/components/DeviceSettings/DeviceSettings.d.ts +5 -1
- package/dist/src/components/DropdownSelect/DropdownSelect.d.ts +14 -0
- package/dist/src/components/DropdownSelect/index.d.ts +1 -0
- package/dist/src/components/Icon/Icon.d.ts +2 -1
- package/dist/src/components/Menu/GenericMenu.d.ts +4 -2
- package/dist/src/components/Menu/MenuToggle.d.ts +15 -2
- package/dist/src/components/Notification/Notification.d.ts +1 -0
- package/dist/src/components/Notification/RecordingInProgressNotification.d.ts +5 -0
- package/dist/src/components/Notification/SpeakingWhileMutedNotification.d.ts +3 -1
- package/dist/src/components/Notification/index.d.ts +1 -0
- package/dist/src/components/index.d.ts +2 -0
- package/dist/src/core/components/ParticipantView/DefaultParticipantViewUI.d.ts +7 -1
- package/dist/src/core/components/ParticipantView/ParticipantActionsContextMenu.d.ts +1 -0
- package/dist/src/core/components/ParticipantView/ParticipantViewContext.d.ts +3 -3
- package/dist/src/core/components/ParticipantView/index.d.ts +1 -0
- package/dist/src/hooks/useFloatingUIPreset.d.ts +4 -1
- package/dist/src/translations/index.d.ts +9 -0
- package/package.json +7 -9
- package/src/components/Button/CompositeButton.tsx +78 -26
- package/src/components/Button/IconButton.tsx +22 -21
- package/src/components/Button/index.ts +0 -1
- package/src/components/CallControls/AcceptCallButton.tsx +1 -0
- package/src/components/CallControls/CallControls.tsx +2 -2
- package/src/components/CallControls/CallStatsButton.tsx +24 -7
- package/src/components/CallControls/CancelCallButton.tsx +102 -3
- package/src/components/CallControls/ReactionsButton.tsx +37 -17
- package/src/components/CallControls/RecordCallButton.tsx +131 -21
- package/src/components/CallControls/ScreenShareButton.tsx +29 -15
- package/src/components/CallControls/ToggleAudioButton.tsx +76 -31
- package/src/components/CallControls/ToggleAudioOutputButton.tsx +20 -10
- package/src/components/CallControls/ToggleVideoButton.tsx +90 -33
- package/src/components/CallParticipantsList/CallParticipantListHeader.tsx +9 -6
- package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +17 -281
- package/src/components/CallParticipantsList/CallParticipantsList.tsx +2 -32
- package/src/components/CallRecordingList/CallRecordingList.tsx +24 -6
- package/src/components/CallRecordingList/CallRecordingListHeader.tsx +6 -2
- package/src/components/CallRecordingList/CallRecordingListItem.tsx +18 -41
- package/src/components/CallStats/CallStats.tsx +167 -10
- package/src/components/CallStats/CallStatsLatencyChart.tsx +73 -44
- package/src/components/DeviceSettings/DeviceSelector.tsx +107 -12
- package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +13 -5
- package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +10 -4
- package/src/components/DeviceSettings/DeviceSettings.tsx +40 -28
- package/src/components/DropdownSelect/DropdownSelect.tsx +214 -0
- package/src/components/DropdownSelect/index.ts +1 -0
- package/src/components/Icon/Icon.tsx +7 -2
- package/src/components/Menu/GenericMenu.tsx +25 -3
- package/src/components/Menu/MenuToggle.tsx +79 -14
- package/src/components/Notification/Notification.tsx +8 -0
- package/src/components/Notification/PermissionNotification.tsx +2 -1
- package/src/components/Notification/RecordingInProgressNotification.tsx +40 -0
- package/src/components/Notification/SpeakingWhileMutedNotification.tsx +9 -1
- package/src/components/Notification/index.ts +1 -0
- package/src/components/Permissions/PermissionRequests.tsx +9 -21
- package/src/components/Search/hooks/useSearch.ts +5 -1
- package/src/components/index.ts +2 -0
- package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +71 -57
- package/src/core/components/ParticipantView/ParticipantActionsContextMenu.tsx +241 -0
- package/src/core/components/ParticipantView/ParticipantView.tsx +2 -2
- package/src/core/components/ParticipantView/ParticipantViewContext.tsx +3 -3
- package/src/core/components/ParticipantView/index.ts +1 -0
- package/src/core/components/Video/BaseVideo.tsx +1 -1
- package/src/core/components/Video/DefaultVideoPlaceholder.tsx +19 -5
- package/src/hooks/useFloatingUIPreset.ts +3 -2
- package/src/hooks/useRequestPermission.ts +2 -1
- package/src/translations/en.json +9 -0
- package/dist/src/components/Button/CopyToClipboardButton.d.ts +0 -27
- package/src/components/Button/CopyToClipboardButton.tsx +0 -129
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SpeakingWhileMutedNotification } from '../Notification';
|
|
2
2
|
import { RecordCallButton } from './RecordCallButton';
|
|
3
|
-
import {
|
|
3
|
+
import { ReactionsButton } from './ReactionsButton';
|
|
4
4
|
import { ScreenShareButton } from './ScreenShareButton';
|
|
5
5
|
import { ToggleAudioPublishingButton } from './ToggleAudioButton';
|
|
6
6
|
import { ToggleVideoPublishingButton } from './ToggleVideoButton';
|
|
@@ -13,7 +13,7 @@ export type CallControlsProps = {
|
|
|
13
13
|
export const CallControls = ({ onLeave }: CallControlsProps) => (
|
|
14
14
|
<div className="str-video__call-controls">
|
|
15
15
|
<RecordCallButton />
|
|
16
|
-
<
|
|
16
|
+
<ReactionsButton />
|
|
17
17
|
<ScreenShareButton />
|
|
18
18
|
<SpeakingWhileMutedNotification>
|
|
19
19
|
<ToggleAudioPublishingButton />
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { forwardRef } from 'react';
|
|
2
|
+
import { useI18n } from '@stream-io/video-react-bindings';
|
|
2
3
|
|
|
3
4
|
import { CallStats } from '../CallStats';
|
|
4
|
-
import { CompositeButton
|
|
5
|
+
import { CompositeButton } from '../Button/';
|
|
5
6
|
import { MenuToggle, ToggleMenuButtonProps } from '../Menu';
|
|
7
|
+
import { Icon } from '../Icon';
|
|
8
|
+
|
|
9
|
+
export type CallStatsButtonProps = {
|
|
10
|
+
caption?: string;
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
export const CallStatsButton = () => (
|
|
8
14
|
<MenuToggle placement="top-end" ToggleButton={ToggleMenuButton}>
|
|
@@ -12,9 +18,20 @@ export const CallStatsButton = () => (
|
|
|
12
18
|
|
|
13
19
|
const ToggleMenuButton = forwardRef<
|
|
14
20
|
HTMLDivElement,
|
|
15
|
-
ToggleMenuButtonProps<HTMLDivElement>
|
|
16
|
-
>((
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
ToggleMenuButtonProps<HTMLDivElement> & CallStatsButtonProps
|
|
22
|
+
>(function ToggleMenuButton(props, ref) {
|
|
23
|
+
const { t } = useI18n();
|
|
24
|
+
const { caption, menuShown } = props;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<CompositeButton
|
|
28
|
+
ref={ref}
|
|
29
|
+
active={menuShown}
|
|
30
|
+
caption={caption}
|
|
31
|
+
title={caption || t('Statistics')}
|
|
32
|
+
data-testid="stats-button"
|
|
33
|
+
>
|
|
34
|
+
<Icon icon="stats" />
|
|
35
|
+
</CompositeButton>
|
|
36
|
+
);
|
|
37
|
+
});
|
|
@@ -1,6 +1,65 @@
|
|
|
1
|
-
import { MouseEventHandler, useCallback } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import { useCall } from '@stream-io/video-react-bindings';
|
|
1
|
+
import { forwardRef, MouseEventHandler, useCallback } from 'react';
|
|
2
|
+
import { OwnCapability } from '@stream-io/video-client';
|
|
3
|
+
import { Restricted, useCall, useI18n } from '@stream-io/video-react-bindings';
|
|
4
|
+
|
|
5
|
+
import { MenuToggle, ToggleMenuButtonProps } from '../Menu';
|
|
6
|
+
|
|
7
|
+
import { IconButton } from '../Button';
|
|
8
|
+
import { Icon } from '../Icon';
|
|
9
|
+
|
|
10
|
+
const EndCallMenu = (props: {
|
|
11
|
+
onLeave: MouseEventHandler<HTMLButtonElement>;
|
|
12
|
+
onEnd: MouseEventHandler<HTMLButtonElement>;
|
|
13
|
+
}) => {
|
|
14
|
+
const { onLeave, onEnd } = props;
|
|
15
|
+
const { t } = useI18n();
|
|
16
|
+
return (
|
|
17
|
+
<div className="str-video__end-call__confirmation">
|
|
18
|
+
<button
|
|
19
|
+
className="str-video__button str-video__end-call__leave"
|
|
20
|
+
type="button"
|
|
21
|
+
data-testid="leave-call-button"
|
|
22
|
+
onClick={onLeave}
|
|
23
|
+
>
|
|
24
|
+
<Icon
|
|
25
|
+
className="str-video__button__icon str-video__end-call__leave-icon"
|
|
26
|
+
icon="logout"
|
|
27
|
+
/>
|
|
28
|
+
{t('Leave call')}
|
|
29
|
+
</button>
|
|
30
|
+
<Restricted requiredGrants={[OwnCapability.END_CALL]}>
|
|
31
|
+
<button
|
|
32
|
+
className="str-video__button str-video__end-call__end"
|
|
33
|
+
type="button"
|
|
34
|
+
data-testid="end-call-for-all-button"
|
|
35
|
+
onClick={onEnd}
|
|
36
|
+
>
|
|
37
|
+
<Icon
|
|
38
|
+
className="str-video__button__icon str-video__end-call__end-icon"
|
|
39
|
+
icon="call-end"
|
|
40
|
+
/>
|
|
41
|
+
{t('End call for all')}
|
|
42
|
+
</button>
|
|
43
|
+
</Restricted>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const CancelCallToggleMenuButton = forwardRef<
|
|
49
|
+
HTMLButtonElement,
|
|
50
|
+
ToggleMenuButtonProps
|
|
51
|
+
>(function CancelCallToggleMenuButton(props, ref) {
|
|
52
|
+
const { t } = useI18n();
|
|
53
|
+
return (
|
|
54
|
+
<IconButton
|
|
55
|
+
icon="call-end"
|
|
56
|
+
variant="danger"
|
|
57
|
+
title={t('Leave call')}
|
|
58
|
+
data-testid="leave-call-button"
|
|
59
|
+
ref={ref}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
});
|
|
4
63
|
|
|
5
64
|
export type CancelCallButtonProps = {
|
|
6
65
|
disabled?: boolean;
|
|
@@ -8,12 +67,50 @@ export type CancelCallButtonProps = {
|
|
|
8
67
|
onLeave?: () => void;
|
|
9
68
|
};
|
|
10
69
|
|
|
70
|
+
export const CancelCallConfirmButton = ({
|
|
71
|
+
onClick,
|
|
72
|
+
onLeave,
|
|
73
|
+
}: CancelCallButtonProps) => {
|
|
74
|
+
const call = useCall();
|
|
75
|
+
|
|
76
|
+
const handleLeave: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
77
|
+
async (e) => {
|
|
78
|
+
if (onClick) {
|
|
79
|
+
onClick(e);
|
|
80
|
+
} else if (call) {
|
|
81
|
+
await call.leave();
|
|
82
|
+
onLeave?.();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[onClick, onLeave, call],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const handleEndCall: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
89
|
+
async (e) => {
|
|
90
|
+
if (onClick) {
|
|
91
|
+
onClick(e);
|
|
92
|
+
} else if (call) {
|
|
93
|
+
await call.endCall();
|
|
94
|
+
onLeave?.();
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[onClick, onLeave, call],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<MenuToggle placement="top-start" ToggleButton={CancelCallToggleMenuButton}>
|
|
102
|
+
<EndCallMenu onEnd={handleEndCall} onLeave={handleLeave} />
|
|
103
|
+
</MenuToggle>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
11
107
|
export const CancelCallButton = ({
|
|
12
108
|
disabled,
|
|
13
109
|
onClick,
|
|
14
110
|
onLeave,
|
|
15
111
|
}: CancelCallButtonProps) => {
|
|
16
112
|
const call = useCall();
|
|
113
|
+
const { t } = useI18n();
|
|
17
114
|
const handleClick: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
18
115
|
async (e) => {
|
|
19
116
|
if (onClick) {
|
|
@@ -30,6 +127,8 @@ export const CancelCallButton = ({
|
|
|
30
127
|
disabled={disabled}
|
|
31
128
|
icon="call-end"
|
|
32
129
|
variant="danger"
|
|
130
|
+
title={t('Leave call')}
|
|
131
|
+
data-testid="cancel-call-button"
|
|
33
132
|
onClick={handleClick}
|
|
34
133
|
/>
|
|
35
134
|
);
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
|
|
1
4
|
import { OwnCapability, StreamReaction } from '@stream-io/video-client';
|
|
2
5
|
import { Restricted, useCall, useI18n } from '@stream-io/video-react-bindings';
|
|
3
6
|
|
|
4
|
-
import {
|
|
7
|
+
import { MenuToggle, MenuVisualType, ToggleMenuButtonProps } from '../Menu';
|
|
8
|
+
import { CompositeButton } from '../Button';
|
|
5
9
|
import { defaultEmojiReactionMap } from '../Reaction';
|
|
10
|
+
import { Icon } from '../Icon';
|
|
6
11
|
|
|
7
12
|
export const defaultReactions: StreamReaction[] = [
|
|
8
13
|
{
|
|
@@ -39,38 +44,53 @@ export interface ReactionsButtonProps {
|
|
|
39
44
|
export const ReactionsButton = ({
|
|
40
45
|
reactions = defaultReactions,
|
|
41
46
|
}: ReactionsButtonProps) => {
|
|
42
|
-
const { t } = useI18n();
|
|
43
|
-
|
|
44
47
|
return (
|
|
45
48
|
<Restricted requiredGrants={[OwnCapability.CREATE_REACTION]}>
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
Menu={<DefaultReactionsMenu reactions={reactions} />}
|
|
49
|
+
<MenuToggle
|
|
50
|
+
placement="top"
|
|
51
|
+
ToggleButton={ToggleReactionsMenuButton}
|
|
52
|
+
visualType={MenuVisualType.MENU}
|
|
51
53
|
>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
title={t('Reactions')}
|
|
55
|
-
onClick={() => {
|
|
56
|
-
console.log('Reactions');
|
|
57
|
-
}}
|
|
58
|
-
/>
|
|
59
|
-
</CompositeButton>
|
|
54
|
+
<DefaultReactionsMenu reactions={reactions} />
|
|
55
|
+
</MenuToggle>
|
|
60
56
|
</Restricted>
|
|
61
57
|
);
|
|
62
58
|
};
|
|
63
59
|
|
|
60
|
+
const ToggleReactionsMenuButton = forwardRef<
|
|
61
|
+
HTMLDivElement,
|
|
62
|
+
ToggleMenuButtonProps
|
|
63
|
+
>(function ToggleReactionsMenuButton({ menuShown }, ref) {
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
return (
|
|
66
|
+
<CompositeButton
|
|
67
|
+
ref={ref}
|
|
68
|
+
active={menuShown}
|
|
69
|
+
variant="primary"
|
|
70
|
+
title={t('Reactions')}
|
|
71
|
+
>
|
|
72
|
+
<Icon icon="reactions" />
|
|
73
|
+
</CompositeButton>
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
64
77
|
export interface DefaultReactionsMenuProps {
|
|
65
78
|
reactions: StreamReaction[];
|
|
79
|
+
layout?: 'horizontal' | 'vertical';
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
export const DefaultReactionsMenu = ({
|
|
69
83
|
reactions,
|
|
84
|
+
layout = 'horizontal',
|
|
70
85
|
}: DefaultReactionsMenuProps) => {
|
|
71
86
|
const call = useCall();
|
|
72
87
|
return (
|
|
73
|
-
<div
|
|
88
|
+
<div
|
|
89
|
+
className={clsx('str-video__reactions-menu', {
|
|
90
|
+
'str-video__reactions-menu--horizontal': layout === 'horizontal',
|
|
91
|
+
'str-video__reactions-menu--vertical': layout === 'vertical',
|
|
92
|
+
})}
|
|
93
|
+
>
|
|
74
94
|
{reactions.map((reaction) => (
|
|
75
95
|
<button
|
|
76
96
|
key={reaction.emoji_code}
|
|
@@ -1,22 +1,94 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
|
|
1
3
|
import { OwnCapability } from '@stream-io/video-client';
|
|
2
|
-
import { Restricted,
|
|
3
|
-
import { CompositeButton
|
|
4
|
+
import { Restricted, useI18n } from '@stream-io/video-react-bindings';
|
|
5
|
+
import { CompositeButton } from '../Button/';
|
|
6
|
+
import { Icon } from '../Icon';
|
|
7
|
+
import {
|
|
8
|
+
MenuToggle,
|
|
9
|
+
MenuVisualType,
|
|
10
|
+
ToggleMenuButtonProps,
|
|
11
|
+
useMenuContext,
|
|
12
|
+
} from '../Menu';
|
|
4
13
|
import { LoadingIndicator } from '../LoadingIndicator';
|
|
5
|
-
import { useToggleCallRecording } from '../../hooks
|
|
14
|
+
import { useToggleCallRecording } from '../../hooks';
|
|
6
15
|
|
|
7
16
|
export type RecordCallButtonProps = {
|
|
8
17
|
caption?: string;
|
|
9
18
|
};
|
|
10
19
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
20
|
+
const RecordEndConfirmation = () => {
|
|
21
|
+
const { t } = useI18n();
|
|
22
|
+
const { toggleCallRecording, isAwaitingResponse } = useToggleCallRecording();
|
|
23
|
+
|
|
24
|
+
const { close } = useMenuContext();
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="str-video__end-recording__confirmation">
|
|
28
|
+
<div className="str-video__end-recording__header">
|
|
29
|
+
<Icon icon="recording-on" />
|
|
30
|
+
<h2 className="str-video__end-recording__heading">
|
|
31
|
+
{t('End recording')}
|
|
32
|
+
</h2>
|
|
33
|
+
</div>
|
|
34
|
+
<p className="str-video__end-recording__description">
|
|
35
|
+
{t('Are you sure you want end the recording?')}
|
|
36
|
+
</p>
|
|
37
|
+
<div className="str-video__end-recording__actions">
|
|
38
|
+
<CompositeButton variant="secondary" onClick={close}>
|
|
39
|
+
{t('Cancel')}
|
|
40
|
+
</CompositeButton>
|
|
41
|
+
<CompositeButton variant="primary" onClick={toggleCallRecording}>
|
|
42
|
+
{isAwaitingResponse ? <LoadingIndicator /> : t('End recording')}
|
|
43
|
+
</CompositeButton>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ToggleEndRecordingMenuButton = forwardRef<
|
|
50
|
+
HTMLDivElement,
|
|
51
|
+
ToggleMenuButtonProps
|
|
52
|
+
>(function ToggleEndRecordingMenuButton(props, ref) {
|
|
53
|
+
return (
|
|
54
|
+
<CompositeButton
|
|
55
|
+
ref={ref}
|
|
56
|
+
active={true}
|
|
57
|
+
variant="secondary"
|
|
58
|
+
data-testid="recording-stop-button"
|
|
59
|
+
>
|
|
60
|
+
<Icon icon="recording-off" />
|
|
61
|
+
</CompositeButton>
|
|
62
|
+
);
|
|
63
|
+
});
|
|
15
64
|
|
|
65
|
+
export const RecordCallConfirmationButton = ({
|
|
66
|
+
caption,
|
|
67
|
+
}: {
|
|
68
|
+
caption?: string;
|
|
69
|
+
}) => {
|
|
16
70
|
const { t } = useI18n();
|
|
17
71
|
const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } =
|
|
18
72
|
useToggleCallRecording();
|
|
19
73
|
|
|
74
|
+
if (isCallRecordingInProgress) {
|
|
75
|
+
return (
|
|
76
|
+
<Restricted
|
|
77
|
+
requiredGrants={[
|
|
78
|
+
OwnCapability.START_RECORD_CALL,
|
|
79
|
+
OwnCapability.STOP_RECORD_CALL,
|
|
80
|
+
]}
|
|
81
|
+
>
|
|
82
|
+
<MenuToggle
|
|
83
|
+
ToggleButton={ToggleEndRecordingMenuButton}
|
|
84
|
+
visualType={MenuVisualType.PORTAL}
|
|
85
|
+
>
|
|
86
|
+
<RecordEndConfirmation />
|
|
87
|
+
</MenuToggle>
|
|
88
|
+
</Restricted>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
20
92
|
return (
|
|
21
93
|
<Restricted
|
|
22
94
|
requiredGrants={[
|
|
@@ -24,23 +96,61 @@ export const RecordCallButton = ({
|
|
|
24
96
|
OwnCapability.STOP_RECORD_CALL,
|
|
25
97
|
]}
|
|
26
98
|
>
|
|
27
|
-
<CompositeButton
|
|
99
|
+
<CompositeButton
|
|
100
|
+
active={isCallRecordingInProgress}
|
|
101
|
+
caption={caption}
|
|
102
|
+
title={caption || t('Record call')}
|
|
103
|
+
variant="secondary"
|
|
104
|
+
data-testid="recording-start-button"
|
|
105
|
+
onClick={isAwaitingResponse ? undefined : toggleCallRecording}
|
|
106
|
+
>
|
|
28
107
|
{isAwaitingResponse ? (
|
|
29
|
-
<LoadingIndicator
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
108
|
+
<LoadingIndicator tooltip={t('Waiting for recording to start...')} />
|
|
109
|
+
) : (
|
|
110
|
+
<Icon icon="recording-off" />
|
|
111
|
+
)}
|
|
112
|
+
</CompositeButton>
|
|
113
|
+
</Restricted>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const RecordCallButton = ({ caption }: RecordCallButtonProps) => {
|
|
118
|
+
const { t } = useI18n();
|
|
119
|
+
const { toggleCallRecording, isAwaitingResponse, isCallRecordingInProgress } =
|
|
120
|
+
useToggleCallRecording();
|
|
121
|
+
|
|
122
|
+
let title = caption || t('Record call');
|
|
123
|
+
|
|
124
|
+
if (isAwaitingResponse) {
|
|
125
|
+
title = isCallRecordingInProgress
|
|
126
|
+
? t('Waiting for recording to stop...')
|
|
127
|
+
: t('Waiting for recording to start...');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<Restricted
|
|
132
|
+
requiredGrants={[
|
|
133
|
+
OwnCapability.START_RECORD_CALL,
|
|
134
|
+
OwnCapability.STOP_RECORD_CALL,
|
|
135
|
+
]}
|
|
136
|
+
>
|
|
137
|
+
<CompositeButton
|
|
138
|
+
active={isCallRecordingInProgress}
|
|
139
|
+
caption={caption}
|
|
140
|
+
variant="secondary"
|
|
141
|
+
data-testid={
|
|
142
|
+
isCallRecordingInProgress
|
|
143
|
+
? 'recording-stop-button'
|
|
144
|
+
: 'recording-start-button'
|
|
145
|
+
}
|
|
146
|
+
title={title}
|
|
147
|
+
onClick={isAwaitingResponse ? undefined : toggleCallRecording}
|
|
148
|
+
>
|
|
149
|
+
{isAwaitingResponse ? (
|
|
150
|
+
<LoadingIndicator />
|
|
36
151
|
) : (
|
|
37
|
-
<
|
|
38
|
-
// FIXME OL: sort out this ambiguity
|
|
39
|
-
enabled={!!call}
|
|
40
|
-
disabled={!call}
|
|
152
|
+
<Icon
|
|
41
153
|
icon={isCallRecordingInProgress ? 'recording-on' : 'recording-off'}
|
|
42
|
-
title={t('Record call')}
|
|
43
|
-
onClick={toggleCallRecording}
|
|
44
154
|
/>
|
|
45
155
|
)}
|
|
46
156
|
</CompositeButton>
|
|
@@ -4,9 +4,10 @@ import {
|
|
|
4
4
|
useCallStateHooks,
|
|
5
5
|
useI18n,
|
|
6
6
|
} from '@stream-io/video-react-bindings';
|
|
7
|
-
import { CompositeButton
|
|
7
|
+
import { CompositeButton } from '../Button/';
|
|
8
8
|
import { PermissionNotification } from '../Notification';
|
|
9
9
|
import { useRequestPermission } from '../../hooks';
|
|
10
|
+
import { Icon } from '../Icon';
|
|
10
11
|
|
|
11
12
|
export type ScreenShareButtonProps = {
|
|
12
13
|
caption?: string;
|
|
@@ -14,16 +15,20 @@ export type ScreenShareButtonProps = {
|
|
|
14
15
|
|
|
15
16
|
export const ScreenShareButton = (props: ScreenShareButtonProps) => {
|
|
16
17
|
const { t } = useI18n();
|
|
17
|
-
const { caption
|
|
18
|
+
const { caption } = props;
|
|
18
19
|
|
|
19
|
-
const { useHasOngoingScreenShare, useScreenShareState } =
|
|
20
|
+
const { useHasOngoingScreenShare, useScreenShareState, useCallSettings } =
|
|
21
|
+
useCallStateHooks();
|
|
20
22
|
const isSomeoneScreenSharing = useHasOngoingScreenShare();
|
|
21
23
|
const { hasPermission, requestPermission, isAwaitingPermission } =
|
|
22
24
|
useRequestPermission(OwnCapability.SCREENSHARE);
|
|
23
25
|
|
|
26
|
+
const callSettings = useCallSettings();
|
|
27
|
+
const isScreenSharingAllowed = callSettings?.screensharing.enabled;
|
|
28
|
+
|
|
24
29
|
const { screenShare, isMute: amIScreenSharing } = useScreenShareState();
|
|
25
30
|
const disableScreenShareButton = amIScreenSharing
|
|
26
|
-
? isSomeoneScreenSharing
|
|
31
|
+
? isSomeoneScreenSharing || isScreenSharingAllowed === false
|
|
27
32
|
: false;
|
|
28
33
|
return (
|
|
29
34
|
<Restricted requiredGrants={[OwnCapability.SCREENSHARE]}>
|
|
@@ -34,20 +39,29 @@ export const ScreenShareButton = (props: ScreenShareButtonProps) => {
|
|
|
34
39
|
messageAwaitingApproval={t('Awaiting for an approval to share screen.')}
|
|
35
40
|
messageRevoked={t('You can no longer share your screen.')}
|
|
36
41
|
>
|
|
37
|
-
<CompositeButton
|
|
38
|
-
|
|
42
|
+
<CompositeButton
|
|
43
|
+
active={isSomeoneScreenSharing}
|
|
44
|
+
caption={caption}
|
|
45
|
+
title={caption || t('Share screen')}
|
|
46
|
+
variant="primary"
|
|
47
|
+
data-testid={
|
|
48
|
+
isSomeoneScreenSharing
|
|
49
|
+
? 'screen-share-stop-button'
|
|
50
|
+
: 'screen-share-start-button'
|
|
51
|
+
}
|
|
52
|
+
disabled={disableScreenShareButton}
|
|
53
|
+
onClick={async () => {
|
|
54
|
+
if (!hasPermission) {
|
|
55
|
+
await requestPermission();
|
|
56
|
+
} else {
|
|
57
|
+
await screenShare.toggle();
|
|
58
|
+
}
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
<Icon
|
|
39
62
|
icon={
|
|
40
63
|
isSomeoneScreenSharing ? 'screen-share-on' : 'screen-share-off'
|
|
41
64
|
}
|
|
42
|
-
title={t('Share screen')}
|
|
43
|
-
disabled={disableScreenShareButton}
|
|
44
|
-
onClick={async () => {
|
|
45
|
-
if (!hasPermission) {
|
|
46
|
-
await requestPermission();
|
|
47
|
-
} else {
|
|
48
|
-
await screenShare.toggle();
|
|
49
|
-
}
|
|
50
|
-
}}
|
|
51
65
|
/>
|
|
52
66
|
</CompositeButton>
|
|
53
67
|
</PermissionNotification>
|
|
@@ -1,55 +1,82 @@
|
|
|
1
|
-
import { ComponentType } from 'react';
|
|
2
1
|
import { OwnCapability } from '@stream-io/video-client';
|
|
3
2
|
import {
|
|
4
3
|
Restricted,
|
|
5
4
|
useCallStateHooks,
|
|
6
5
|
useI18n,
|
|
7
6
|
} from '@stream-io/video-react-bindings';
|
|
7
|
+
import clsx from 'clsx';
|
|
8
|
+
import { CompositeButton, IconButtonWithMenuProps } from '../Button';
|
|
8
9
|
import { DeviceSelectorAudioInput } from '../DeviceSettings';
|
|
9
|
-
import { CompositeButton, IconButton } from '../Button';
|
|
10
10
|
import { PermissionNotification } from '../Notification';
|
|
11
11
|
import { useRequestPermission } from '../../hooks';
|
|
12
|
+
import { Icon } from '../Icon';
|
|
12
13
|
|
|
13
|
-
export type ToggleAudioPreviewButtonProps =
|
|
14
|
-
|
|
15
|
-
Menu
|
|
16
|
-
|
|
14
|
+
export type ToggleAudioPreviewButtonProps = Pick<
|
|
15
|
+
IconButtonWithMenuProps,
|
|
16
|
+
'caption' | 'Menu' | 'menuPlacement'
|
|
17
|
+
>;
|
|
17
18
|
|
|
18
19
|
export const ToggleAudioPreviewButton = (
|
|
19
20
|
props: ToggleAudioPreviewButtonProps,
|
|
20
21
|
) => {
|
|
22
|
+
const { caption, Menu, menuPlacement, ...restCompositeButtonProps } = props;
|
|
21
23
|
const { t } = useI18n();
|
|
22
|
-
const { caption = t('Mic'), Menu = DeviceSelectorAudioInput } = props;
|
|
23
|
-
|
|
24
24
|
const { useMicrophoneState } = useCallStateHooks();
|
|
25
|
-
const { microphone, isMute } = useMicrophoneState();
|
|
25
|
+
const { microphone, isMute, hasBrowserPermission } = useMicrophoneState();
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
<CompositeButton
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
<CompositeButton
|
|
29
|
+
active={isMute}
|
|
30
|
+
caption={caption}
|
|
31
|
+
className={clsx(!hasBrowserPermission && 'str-video__device-unavailable')}
|
|
32
|
+
variant="secondary"
|
|
33
|
+
title={
|
|
34
|
+
!hasBrowserPermission
|
|
35
|
+
? t('Check your browser audio permissions')
|
|
36
|
+
: caption || t('Mic')
|
|
37
|
+
}
|
|
38
|
+
disabled={!hasBrowserPermission}
|
|
39
|
+
data-testid={
|
|
40
|
+
isMute ? 'preview-audio-unmute-button' : 'preview-audio-mute-button'
|
|
41
|
+
}
|
|
42
|
+
onClick={() => microphone.toggle()}
|
|
43
|
+
Menu={Menu}
|
|
44
|
+
menuPlacement={menuPlacement}
|
|
45
|
+
{...restCompositeButtonProps}
|
|
46
|
+
>
|
|
47
|
+
<Icon icon={!isMute ? 'mic' : 'mic-off'} />
|
|
48
|
+
{!hasBrowserPermission && (
|
|
49
|
+
<span
|
|
50
|
+
className="str-video__no-media-permission"
|
|
51
|
+
title={t('Check your browser audio permissions')}
|
|
52
|
+
children="!"
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
33
55
|
</CompositeButton>
|
|
34
56
|
);
|
|
35
57
|
};
|
|
36
58
|
|
|
37
|
-
export type ToggleAudioPublishingButtonProps =
|
|
38
|
-
|
|
39
|
-
Menu
|
|
40
|
-
|
|
59
|
+
export type ToggleAudioPublishingButtonProps = Pick<
|
|
60
|
+
IconButtonWithMenuProps,
|
|
61
|
+
'caption' | 'Menu' | 'menuPlacement'
|
|
62
|
+
>;
|
|
41
63
|
|
|
42
64
|
export const ToggleAudioPublishingButton = (
|
|
43
65
|
props: ToggleAudioPublishingButtonProps,
|
|
44
66
|
) => {
|
|
45
67
|
const { t } = useI18n();
|
|
46
|
-
const {
|
|
68
|
+
const {
|
|
69
|
+
caption,
|
|
70
|
+
Menu = <DeviceSelectorAudioInput visualType="list" />,
|
|
71
|
+
menuPlacement = 'top',
|
|
72
|
+
...restCompositeButtonProps
|
|
73
|
+
} = props;
|
|
47
74
|
|
|
48
75
|
const { hasPermission, requestPermission, isAwaitingPermission } =
|
|
49
76
|
useRequestPermission(OwnCapability.SEND_AUDIO);
|
|
50
77
|
|
|
51
78
|
const { useMicrophoneState } = useCallStateHooks();
|
|
52
|
-
const { microphone, isMute } = useMicrophoneState();
|
|
79
|
+
const { microphone, isMute, hasBrowserPermission } = useMicrophoneState();
|
|
53
80
|
|
|
54
81
|
return (
|
|
55
82
|
<Restricted requiredGrants={[OwnCapability.SEND_AUDIO]}>
|
|
@@ -60,17 +87,35 @@ export const ToggleAudioPublishingButton = (
|
|
|
60
87
|
messageAwaitingApproval={t('Awaiting for an approval to speak.')}
|
|
61
88
|
messageRevoked={t('You can no longer speak.')}
|
|
62
89
|
>
|
|
63
|
-
<CompositeButton
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
90
|
+
<CompositeButton
|
|
91
|
+
active={isMute}
|
|
92
|
+
caption={caption}
|
|
93
|
+
title={
|
|
94
|
+
!hasPermission
|
|
95
|
+
? t('You have no permission to share your audio')
|
|
96
|
+
: !hasBrowserPermission
|
|
97
|
+
? t('Check your browser mic permissions')
|
|
98
|
+
: caption || t('Mic')
|
|
99
|
+
}
|
|
100
|
+
variant="secondary"
|
|
101
|
+
disabled={!hasBrowserPermission || !hasPermission}
|
|
102
|
+
data-testid={isMute ? 'audio-unmute-button' : 'audio-mute-button'}
|
|
103
|
+
onClick={async () => {
|
|
104
|
+
if (!hasPermission) {
|
|
105
|
+
await requestPermission();
|
|
106
|
+
} else {
|
|
107
|
+
await microphone.toggle();
|
|
108
|
+
}
|
|
109
|
+
}}
|
|
110
|
+
Menu={Menu}
|
|
111
|
+
menuPlacement={menuPlacement}
|
|
112
|
+
menuOffset={16}
|
|
113
|
+
{...restCompositeButtonProps}
|
|
114
|
+
>
|
|
115
|
+
<Icon icon={isMute ? 'mic-off' : 'mic'} />
|
|
116
|
+
{(!hasBrowserPermission || !hasPermission) && (
|
|
117
|
+
<span className="str-video__no-media-permission">!</span>
|
|
118
|
+
)}
|
|
74
119
|
</CompositeButton>
|
|
75
120
|
</PermissionNotification>
|
|
76
121
|
</Restricted>
|