@tellescope/react-components 1.246.2 → 1.248.0
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/lib/cjs/Forms/form_responses.d.ts +11 -0
- package/lib/cjs/Forms/form_responses.d.ts.map +1 -1
- package/lib/cjs/Forms/form_responses.js +38 -2
- package/lib/cjs/Forms/form_responses.js.map +1 -1
- package/lib/cjs/Forms/forms.d.ts +2 -1
- package/lib/cjs/Forms/forms.d.ts.map +1 -1
- package/lib/cjs/Forms/forms.js +34 -8
- package/lib/cjs/Forms/forms.js.map +1 -1
- package/lib/cjs/Forms/forms.v2.js +1 -1
- package/lib/cjs/Forms/forms.v2.js.map +1 -1
- package/lib/cjs/Forms/hooks.d.ts +1 -0
- package/lib/cjs/Forms/hooks.d.ts.map +1 -1
- package/lib/cjs/Forms/hooks.js +5 -4
- package/lib/cjs/Forms/hooks.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioControls.d.ts +2 -0
- package/lib/cjs/TwilioVideo/TwilioControls.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioControls.js +14 -3
- package/lib/cjs/TwilioVideo/TwilioControls.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioParticipant.d.ts +2 -0
- package/lib/cjs/TwilioVideo/TwilioParticipant.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioParticipant.js +52 -21
- package/lib/cjs/TwilioVideo/TwilioParticipant.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoContext.d.ts +5 -0
- package/lib/cjs/TwilioVideo/TwilioVideoContext.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoContext.js +107 -5
- package/lib/cjs/TwilioVideo/TwilioVideoContext.js.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoRoom.d.ts +2 -0
- package/lib/cjs/TwilioVideo/TwilioVideoRoom.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/TwilioVideoRoom.js +49 -3
- package/lib/cjs/TwilioVideo/TwilioVideoRoom.js.map +1 -1
- package/lib/cjs/TwilioVideo/index.d.ts +1 -1
- package/lib/cjs/TwilioVideo/index.d.ts.map +1 -1
- package/lib/cjs/TwilioVideo/index.js +2 -1
- package/lib/cjs/TwilioVideo/index.js.map +1 -1
- package/lib/cjs/state.d.ts +7 -1
- package/lib/cjs/state.d.ts.map +1 -1
- package/lib/cjs/state.js +43 -1
- package/lib/cjs/state.js.map +1 -1
- package/lib/esm/Forms/form_responses.d.ts +11 -0
- package/lib/esm/Forms/form_responses.d.ts.map +1 -1
- package/lib/esm/Forms/form_responses.js +37 -2
- package/lib/esm/Forms/form_responses.js.map +1 -1
- package/lib/esm/Forms/forms.d.ts +2 -1
- package/lib/esm/Forms/forms.d.ts.map +1 -1
- package/lib/esm/Forms/forms.js +34 -8
- package/lib/esm/Forms/forms.js.map +1 -1
- package/lib/esm/Forms/forms.v2.js +1 -1
- package/lib/esm/Forms/forms.v2.js.map +1 -1
- package/lib/esm/Forms/hooks.d.ts +1 -0
- package/lib/esm/Forms/hooks.d.ts.map +1 -1
- package/lib/esm/Forms/hooks.js +6 -5
- package/lib/esm/Forms/hooks.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioControls.d.ts +2 -0
- package/lib/esm/TwilioVideo/TwilioControls.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioControls.js +15 -4
- package/lib/esm/TwilioVideo/TwilioControls.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioParticipant.d.ts +2 -0
- package/lib/esm/TwilioVideo/TwilioParticipant.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioParticipant.js +52 -21
- package/lib/esm/TwilioVideo/TwilioParticipant.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoContext.d.ts +5 -0
- package/lib/esm/TwilioVideo/TwilioVideoContext.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoContext.js +83 -1
- package/lib/esm/TwilioVideo/TwilioVideoContext.js.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoRoom.d.ts +2 -0
- package/lib/esm/TwilioVideo/TwilioVideoRoom.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/TwilioVideoRoom.js +49 -3
- package/lib/esm/TwilioVideo/TwilioVideoRoom.js.map +1 -1
- package/lib/esm/TwilioVideo/index.d.ts +1 -1
- package/lib/esm/TwilioVideo/index.d.ts.map +1 -1
- package/lib/esm/TwilioVideo/index.js +1 -1
- package/lib/esm/TwilioVideo/index.js.map +1 -1
- package/lib/esm/state.d.ts +7 -1
- package/lib/esm/state.d.ts.map +1 -1
- package/lib/esm/state.js +42 -1
- package/lib/esm/state.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/src/Forms/form_responses.tsx +133 -2
- package/src/Forms/forms.tsx +32 -6
- package/src/Forms/forms.v2.tsx +1 -1
- package/src/Forms/hooks.tsx +9 -5
- package/src/TwilioVideo/TwilioControls.tsx +30 -0
- package/src/TwilioVideo/TwilioParticipant.tsx +51 -17
- package/src/TwilioVideo/TwilioVideoContext.tsx +78 -0
- package/src/TwilioVideo/TwilioVideoRoom.tsx +92 -2
- package/src/TwilioVideo/index.ts +1 -0
- package/src/state.tsx +37 -0
|
@@ -9,12 +9,17 @@ import Video, {
|
|
|
9
9
|
LocalParticipant,
|
|
10
10
|
} from 'twilio-video'
|
|
11
11
|
|
|
12
|
+
export const SCREEN_SHARE_TRACK_NAME = 'screen-share'
|
|
13
|
+
|
|
12
14
|
export interface TwilioVideoState {
|
|
13
15
|
room: Room | null
|
|
14
16
|
isConnecting: boolean
|
|
15
17
|
isConnected: boolean
|
|
16
18
|
localVideoTrack: LocalVideoTrack | null
|
|
17
19
|
localAudioTrack: LocalAudioTrack | null
|
|
20
|
+
localScreenTrack: LocalVideoTrack | null
|
|
21
|
+
isScreenSharing: boolean
|
|
22
|
+
screenSharingParticipantSid: string | null
|
|
18
23
|
participants: RemoteParticipant[]
|
|
19
24
|
error: Error | null
|
|
20
25
|
isHost: boolean
|
|
@@ -27,6 +32,7 @@ export interface TwilioVideoActions {
|
|
|
27
32
|
disconnect: () => void
|
|
28
33
|
toggleVideo: () => Promise<void>
|
|
29
34
|
toggleAudio: () => void
|
|
35
|
+
toggleScreenShare: () => Promise<void>
|
|
30
36
|
setIsHost: (isHost: boolean) => void
|
|
31
37
|
}
|
|
32
38
|
|
|
@@ -56,6 +62,9 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
56
62
|
const [isHost, setIsHost] = useState(false)
|
|
57
63
|
const [isVideoEnabled, setIsVideoEnabled] = useState(true)
|
|
58
64
|
const [isAudioEnabled, setIsAudioEnabled] = useState(true)
|
|
65
|
+
const [localScreenTrack, setLocalScreenTrack] = useState<LocalVideoTrack | null>(null)
|
|
66
|
+
const [isScreenSharing, setIsScreenSharing] = useState(false)
|
|
67
|
+
const [screenSharingParticipantSid, setScreenSharingParticipantSid] = useState<string | null>(null)
|
|
59
68
|
|
|
60
69
|
const localTracksRef = useRef<(LocalVideoTrack | LocalAudioTrack)[]>([])
|
|
61
70
|
|
|
@@ -101,6 +110,18 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
101
110
|
setParticipants(prev => prev.filter(p => p.sid !== participant.sid))
|
|
102
111
|
})
|
|
103
112
|
|
|
113
|
+
// Track remote screen sharing for React re-renders
|
|
114
|
+
newRoom.on('trackSubscribed', (track: RemoteTrack, publication, participant: RemoteParticipant) => {
|
|
115
|
+
if (track.kind === 'video' && track.name === SCREEN_SHARE_TRACK_NAME) {
|
|
116
|
+
setScreenSharingParticipantSid(participant.sid)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
newRoom.on('trackUnsubscribed', (track: RemoteTrack, publication, participant: RemoteParticipant) => {
|
|
120
|
+
if (track.kind === 'video' && track.name === SCREEN_SHARE_TRACK_NAME) {
|
|
121
|
+
setScreenSharingParticipantSid(null)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
104
125
|
newRoom.on('disconnected', () => {
|
|
105
126
|
// Stop all local tracks when disconnected
|
|
106
127
|
localTracksRef.current.forEach(track => {
|
|
@@ -110,6 +131,9 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
110
131
|
setRoom(null)
|
|
111
132
|
setLocalVideoTrack(null)
|
|
112
133
|
setLocalAudioTrack(null)
|
|
134
|
+
setLocalScreenTrack(null)
|
|
135
|
+
setIsScreenSharing(false)
|
|
136
|
+
setScreenSharingParticipantSid(null)
|
|
113
137
|
setParticipants([])
|
|
114
138
|
})
|
|
115
139
|
|
|
@@ -135,6 +159,9 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
135
159
|
setRoom(null)
|
|
136
160
|
setLocalVideoTrack(null)
|
|
137
161
|
setLocalAudioTrack(null)
|
|
162
|
+
setLocalScreenTrack(null)
|
|
163
|
+
setIsScreenSharing(false)
|
|
164
|
+
setScreenSharingParticipantSid(null)
|
|
138
165
|
setParticipants([])
|
|
139
166
|
}, [room])
|
|
140
167
|
|
|
@@ -160,6 +187,53 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
160
187
|
}
|
|
161
188
|
}, [localAudioTrack, isAudioEnabled])
|
|
162
189
|
|
|
190
|
+
const stopScreenShare = useCallback(() => {
|
|
191
|
+
if (localScreenTrack) {
|
|
192
|
+
if (room) {
|
|
193
|
+
room.localParticipant.unpublishTrack(localScreenTrack)
|
|
194
|
+
}
|
|
195
|
+
localScreenTrack.stop()
|
|
196
|
+
localTracksRef.current = localTracksRef.current.filter(t => t !== localScreenTrack)
|
|
197
|
+
setLocalScreenTrack(null)
|
|
198
|
+
setIsScreenSharing(false)
|
|
199
|
+
}
|
|
200
|
+
}, [localScreenTrack, room])
|
|
201
|
+
|
|
202
|
+
const toggleScreenShare = useCallback(async () => {
|
|
203
|
+
if (isScreenSharing) {
|
|
204
|
+
stopScreenShare()
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true })
|
|
210
|
+
const mediaStreamTrack = stream.getVideoTracks()[0]
|
|
211
|
+
const screenTrack = new LocalVideoTrack(mediaStreamTrack, { name: SCREEN_SHARE_TRACK_NAME })
|
|
212
|
+
|
|
213
|
+
if (room) {
|
|
214
|
+
await room.localParticipant.publishTrack(screenTrack)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
localTracksRef.current.push(screenTrack)
|
|
218
|
+
setLocalScreenTrack(screenTrack)
|
|
219
|
+
setIsScreenSharing(true)
|
|
220
|
+
|
|
221
|
+
// Handle browser "Stop sharing" button
|
|
222
|
+
mediaStreamTrack.onended = () => {
|
|
223
|
+
if (room) {
|
|
224
|
+
room.localParticipant.unpublishTrack(screenTrack)
|
|
225
|
+
}
|
|
226
|
+
screenTrack.stop()
|
|
227
|
+
localTracksRef.current = localTracksRef.current.filter(t => t !== screenTrack)
|
|
228
|
+
setLocalScreenTrack(null)
|
|
229
|
+
setIsScreenSharing(false)
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
// User cancelled the screen share picker — not an error
|
|
233
|
+
console.log('Screen share cancelled or failed:', err)
|
|
234
|
+
}
|
|
235
|
+
}, [isScreenSharing, stopScreenShare, room])
|
|
236
|
+
|
|
163
237
|
// Cleanup on unmount
|
|
164
238
|
useEffect(() => {
|
|
165
239
|
return () => {
|
|
@@ -178,6 +252,9 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
178
252
|
isConnected: !!room,
|
|
179
253
|
localVideoTrack,
|
|
180
254
|
localAudioTrack,
|
|
255
|
+
localScreenTrack,
|
|
256
|
+
isScreenSharing,
|
|
257
|
+
screenSharingParticipantSid,
|
|
181
258
|
participants,
|
|
182
259
|
error,
|
|
183
260
|
isHost,
|
|
@@ -187,6 +264,7 @@ export const TwilioVideoProvider: React.FC<TwilioVideoProviderProps> = ({ childr
|
|
|
187
264
|
disconnect,
|
|
188
265
|
toggleVideo,
|
|
189
266
|
toggleAudio,
|
|
267
|
+
toggleScreenShare,
|
|
190
268
|
setIsHost,
|
|
191
269
|
}
|
|
192
270
|
|
|
@@ -3,6 +3,7 @@ import { Box, Grid } from '@mui/material'
|
|
|
3
3
|
import { useTwilioVideo } from './TwilioVideoContext'
|
|
4
4
|
import { TwilioParticipant } from './TwilioParticipant'
|
|
5
5
|
import { TwilioControlBar } from './TwilioControls'
|
|
6
|
+
import { RemoteParticipant, LocalParticipant } from 'twilio-video'
|
|
6
7
|
|
|
7
8
|
export interface TwilioVideoRoomProps {
|
|
8
9
|
onLeave?: () => void
|
|
@@ -10,6 +11,8 @@ export interface TwilioVideoRoomProps {
|
|
|
10
11
|
style?: React.CSSProperties
|
|
11
12
|
/** Resolve participant identity to a display label. Defaults to empty string. */
|
|
12
13
|
resolveIdentity?: (identity: string) => string
|
|
14
|
+
/** Whether to show the screen share button. Defaults to true. */
|
|
15
|
+
showScreenShare?: boolean
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
export const TwilioVideoRoom: React.FC<TwilioVideoRoomProps> = ({
|
|
@@ -17,14 +20,101 @@ export const TwilioVideoRoom: React.FC<TwilioVideoRoomProps> = ({
|
|
|
17
20
|
onEndForAll,
|
|
18
21
|
style,
|
|
19
22
|
resolveIdentity,
|
|
23
|
+
showScreenShare: showScreenShareProp = true,
|
|
20
24
|
}) => {
|
|
21
|
-
const { room, participants } = useTwilioVideo()
|
|
25
|
+
const { room, participants, isScreenSharing, screenSharingParticipantSid } = useTwilioVideo()
|
|
22
26
|
|
|
23
27
|
if (!room) return null
|
|
24
28
|
|
|
25
29
|
const localParticipant = room.localParticipant
|
|
26
30
|
const hasRemoteParticipants = participants.length > 0
|
|
27
31
|
|
|
32
|
+
// Find who is sharing their screen (context-driven so React re-renders properly)
|
|
33
|
+
const screenShareParticipant: RemoteParticipant | LocalParticipant | null = (() => {
|
|
34
|
+
if (isScreenSharing) return localParticipant
|
|
35
|
+
if (screenSharingParticipantSid) {
|
|
36
|
+
return participants.find(p => p.sid === screenSharingParticipantSid) || null
|
|
37
|
+
}
|
|
38
|
+
return null
|
|
39
|
+
})()
|
|
40
|
+
|
|
41
|
+
const isScreenShareActive = screenShareParticipant !== null
|
|
42
|
+
|
|
43
|
+
// All participants for the camera strip (local + remote)
|
|
44
|
+
const allParticipants: (RemoteParticipant | LocalParticipant)[] = [
|
|
45
|
+
localParticipant,
|
|
46
|
+
...participants,
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
if (isScreenShareActive) {
|
|
50
|
+
// Presentation layout: screen share large on top, camera strip on bottom
|
|
51
|
+
return (
|
|
52
|
+
<Box
|
|
53
|
+
sx={{
|
|
54
|
+
display: 'flex',
|
|
55
|
+
flexDirection: 'column',
|
|
56
|
+
height: '100%',
|
|
57
|
+
width: '100%',
|
|
58
|
+
backgroundColor: '#1a1a1a',
|
|
59
|
+
...style,
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
{/* Screen share - main area */}
|
|
63
|
+
<Box
|
|
64
|
+
sx={{
|
|
65
|
+
flex: 1,
|
|
66
|
+
overflow: 'hidden',
|
|
67
|
+
minHeight: 0,
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<TwilioParticipant
|
|
71
|
+
participant={screenShareParticipant}
|
|
72
|
+
isLocal={screenShareParticipant === localParticipant}
|
|
73
|
+
showScreenShare
|
|
74
|
+
resolveIdentity={resolveIdentity}
|
|
75
|
+
/>
|
|
76
|
+
</Box>
|
|
77
|
+
|
|
78
|
+
{/* Camera strip */}
|
|
79
|
+
<Box
|
|
80
|
+
sx={{
|
|
81
|
+
height: 120,
|
|
82
|
+
display: 'flex',
|
|
83
|
+
flexDirection: 'row',
|
|
84
|
+
gap: 1,
|
|
85
|
+
padding: 1,
|
|
86
|
+
overflowX: 'auto',
|
|
87
|
+
flexShrink: 0,
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
{allParticipants.map((p) => (
|
|
91
|
+
<Box
|
|
92
|
+
key={p.sid}
|
|
93
|
+
sx={{
|
|
94
|
+
height: '100%',
|
|
95
|
+
aspectRatio: '4/3',
|
|
96
|
+
flexShrink: 0,
|
|
97
|
+
borderRadius: 1,
|
|
98
|
+
overflow: 'hidden',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<TwilioParticipant
|
|
102
|
+
participant={p}
|
|
103
|
+
isLocal={p === localParticipant}
|
|
104
|
+
showScreenShare={false}
|
|
105
|
+
resolveIdentity={resolveIdentity}
|
|
106
|
+
/>
|
|
107
|
+
</Box>
|
|
108
|
+
))}
|
|
109
|
+
</Box>
|
|
110
|
+
|
|
111
|
+
{/* Control bar */}
|
|
112
|
+
<TwilioControlBar onLeave={onLeave} onEndForAll={onEndForAll} showScreenShare={showScreenShareProp} />
|
|
113
|
+
</Box>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Normal layout (no screen share active)
|
|
28
118
|
return (
|
|
29
119
|
<Box
|
|
30
120
|
sx={{
|
|
@@ -92,7 +182,7 @@ export const TwilioVideoRoom: React.FC<TwilioVideoRoomProps> = ({
|
|
|
92
182
|
</Box>
|
|
93
183
|
|
|
94
184
|
{/* Control bar */}
|
|
95
|
-
<TwilioControlBar onLeave={onLeave} onEndForAll={onEndForAll} />
|
|
185
|
+
<TwilioControlBar onLeave={onLeave} onEndForAll={onEndForAll} showScreenShare={showScreenShareProp} />
|
|
96
186
|
</Box>
|
|
97
187
|
)
|
|
98
188
|
}
|
package/src/TwilioVideo/index.ts
CHANGED
package/src/state.tsx
CHANGED
|
@@ -2637,6 +2637,43 @@ export const useIntegrations = (options={} as HookOptions<Integration>) => {
|
|
|
2637
2637
|
)
|
|
2638
2638
|
}
|
|
2639
2639
|
|
|
2640
|
+
export const useRedactedIntegrations = () => {
|
|
2641
|
+
const session = useSession()
|
|
2642
|
+
const [loadingState, setLoadingState] = useState<LoadedData<Integration[]>>({ status: LoadingStatus.Fetching, value: [] as any })
|
|
2643
|
+
const fetchedRef = useRef(false)
|
|
2644
|
+
|
|
2645
|
+
useEffect(() => {
|
|
2646
|
+
if (fetchedRef.current) return
|
|
2647
|
+
fetchedRef.current = true
|
|
2648
|
+
|
|
2649
|
+
let cancelled = false
|
|
2650
|
+
session.api.integrations.load_redacted({})
|
|
2651
|
+
.then((result: any) => {
|
|
2652
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Loaded, value: result.integrations })
|
|
2653
|
+
})
|
|
2654
|
+
.catch((err: any) => {
|
|
2655
|
+
if (!cancelled) setLoadingState({ status: LoadingStatus.Error, value: err })
|
|
2656
|
+
})
|
|
2657
|
+
return () => { cancelled = true }
|
|
2658
|
+
}, [session])
|
|
2659
|
+
|
|
2660
|
+
const updateIntegration = useCallback(async (id: string, updates: Partial<Integration>) => {
|
|
2661
|
+
const result: any = await session.api.integrations.update_settings({ id, updates })
|
|
2662
|
+
setLoadingState((prev: LoadedData<Integration[]>) => {
|
|
2663
|
+
if (prev.status !== LoadingStatus.Loaded) return prev
|
|
2664
|
+
return {
|
|
2665
|
+
...prev,
|
|
2666
|
+
value: prev.value.map((i: Integration) => i.id === id ? { ...i, ...result.integration } : i),
|
|
2667
|
+
}
|
|
2668
|
+
})
|
|
2669
|
+
}, [session])
|
|
2670
|
+
|
|
2671
|
+
return [
|
|
2672
|
+
loadingState,
|
|
2673
|
+
{ updateIntegration },
|
|
2674
|
+
] as const
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2640
2677
|
export const usePortalCustomizations = (options={} as HookOptions<PortalCustomization>) => {
|
|
2641
2678
|
const session = useResolvedSession()
|
|
2642
2679
|
return useListStateHook(
|