1
- use super :: icons :: push_pin :: PushPinIcon ;
2
- use crate :: constants:: { USERS_ALLOWED_TO_STREAM , WEBTRANSPORT_HOST } ;
1
+ use crate :: components :: { canvas_generator , peer_list :: PeerList } ;
2
+ use crate :: constants:: { CANVAS_LIMIT , USERS_ALLOWED_TO_STREAM , WEBTRANSPORT_HOST } ;
3
3
use crate :: { components:: host:: Host , constants:: ACTIX_WEBSOCKET } ;
4
4
use log:: { error, warn} ;
5
- use std:: rc:: Rc ;
6
5
use types:: protos:: media_packet:: media_packet:: MediaType ;
7
6
use videocall_client:: { MediaDeviceAccess , VideoCallClient , VideoCallClientOptions } ;
8
- use wasm_bindgen:: JsCast ;
9
7
use wasm_bindgen:: JsValue ;
10
8
use web_sys:: * ;
11
9
use yew:: prelude:: * ;
12
- use yew:: virtual_dom:: VNode ;
13
10
use yew:: { html, Component , Context , Html } ;
14
11
15
12
#[ derive( Debug ) ]
@@ -31,11 +28,16 @@ pub enum MeetingAction {
31
28
ToggleVideoOnOff ,
32
29
}
33
30
31
+ pub enum UserScreenAction {
32
+ TogglePeerList ,
33
+ }
34
+
34
35
pub enum Msg {
35
36
WsAction ( WsAction ) ,
36
37
MeetingAction ( MeetingAction ) ,
37
38
OnPeerAdded ( String ) ,
38
39
OnFirstFrame ( ( String , MediaType ) ) ,
40
+ UserScreenAction ( UserScreenAction ) ,
39
41
}
40
42
41
43
impl From < WsAction > for Msg {
@@ -44,6 +46,12 @@ impl From<WsAction> for Msg {
44
46
}
45
47
}
46
48
49
+ impl From < UserScreenAction > for Msg {
50
+ fn from ( action : UserScreenAction ) -> Self {
51
+ Msg :: UserScreenAction ( action)
52
+ }
53
+ }
54
+
47
55
impl From < MeetingAction > for Msg {
48
56
fn from ( action : MeetingAction ) -> Self {
49
57
Msg :: MeetingAction ( action)
@@ -69,6 +77,7 @@ pub struct AttendantsComponent {
69
77
pub share_screen : bool ,
70
78
pub mic_enabled : bool ,
71
79
pub video_enabled : bool ,
80
+ pub peer_list_open : bool ,
72
81
pub error : Option < String > ,
73
82
}
74
83
@@ -133,6 +142,7 @@ impl Component for AttendantsComponent {
133
142
share_screen : false ,
134
143
mic_enabled : false ,
135
144
video_enabled : false ,
145
+ peer_list_open : false ,
136
146
error : None ,
137
147
}
138
148
}
@@ -197,162 +207,93 @@ impl Component for AttendantsComponent {
197
207
}
198
208
true
199
209
}
210
+ Msg :: UserScreenAction ( action) => {
211
+ match action {
212
+ UserScreenAction :: TogglePeerList => {
213
+ self . peer_list_open = !self . peer_list_open ;
214
+ }
215
+ }
216
+ true
217
+ }
200
218
}
201
219
}
202
220
203
221
fn view ( & self , ctx : & Context < Self > ) -> Html {
204
222
let email = ctx. props ( ) . email . clone ( ) ;
205
223
let media_access_granted = self . media_device_access . is_granted ( ) ;
206
- let rows: Vec < VNode > = self
207
- . client
208
- . sorted_peer_keys ( )
209
- . iter ( )
210
- . map ( |key| {
211
- if !USERS_ALLOWED_TO_STREAM . is_empty ( )
212
- && !USERS_ALLOWED_TO_STREAM . iter ( ) . any ( |host| host == key)
213
- {
214
- return html ! { } ;
215
- }
216
- let screen_share_css = if self . client . is_awaiting_peer_screen_frame ( key) {
217
- "grid-item hidden"
218
- } else {
219
- "grid-item"
220
- } ;
221
- let screen_share_div_id = Rc :: new ( format ! ( "screen-share-{}-div" , & key) ) ;
222
- let peer_video_div_id = Rc :: new ( format ! ( "peer-video-{}-div" , & key) ) ;
223
- html ! {
224
- <>
225
- <div class={ screen_share_css} id={ ( * screen_share_div_id) . clone( ) } >
226
- // Canvas for Screen share.
227
- <div class="canvas-container" >
228
- <canvas id={ format!( "screen-share-{}" , & key) } ></canvas>
229
- <h4 class="floating-name" >{ format!( "{}-screen" , & key) } </h4>
230
- <button onclick={ Callback :: from( move |_| {
231
- toggle_pinned_div( & ( * screen_share_div_id) . clone( ) ) ;
232
- } ) } class="pin-icon" >
233
- <PushPinIcon />
234
- </button>
235
- </div>
236
- </div>
237
- <div class="grid-item" id={ ( * peer_video_div_id) . clone( ) } >
238
- // One canvas for the User Video
239
- <div class="canvas-container" >
240
- <UserVideo id={ key. clone( ) } ></UserVideo >
241
- <h4 class="floating-name" >{ key. clone( ) } </h4>
242
- <button onclick={
243
- Callback :: from( move |_| {
244
- toggle_pinned_div( & ( * peer_video_div_id) . clone( ) ) ;
245
- } ) } class="pin-icon" >
246
- <PushPinIcon />
247
- </button>
248
- </div>
249
- </div>
250
- </>
251
- }
252
- } )
253
- . collect ( ) ;
224
+
225
+ let toggle_peer_list = ctx. link ( ) . callback ( |_| UserScreenAction :: TogglePeerList ) ;
226
+
227
+ let peers = self . client . sorted_peer_keys ( ) ;
228
+ let rows = canvas_generator:: generate (
229
+ & self . client ,
230
+ peers. iter ( ) . take ( CANVAS_LIMIT ) . cloned ( ) . collect ( ) ,
231
+ ) ;
232
+
254
233
html ! {
255
- <div class="grid-container" >
256
- { self . error. as_ref( ) . map( |error| html! { <p>{ error } </p> } ) }
257
- { rows }
258
- {
259
- if USERS_ALLOWED_TO_STREAM . iter( ) . any( |host| host == & email) || USERS_ALLOWED_TO_STREAM . is_empty( ) {
260
- html! {
261
- <nav class="host" >
262
- <div class="controls" >
263
- <button
264
- class="bg-yew-blue p-2 rounded-md text-white"
265
- onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleScreenShare ) } >
266
- { if self . share_screen { "Stop Screen Share" } else { "Share Screen" } }
267
- </button>
268
- <button
269
- class="bg-yew-blue p-2 rounded-md text-white"
270
- onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleVideoOnOff ) } >
271
- { if !self . video_enabled { "Start Video" } else { "Stop Video" } }
272
- </button>
273
- <button
274
- class="bg-yew-blue p-2 rounded-md text-white"
275
- onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleMicMute ) } >
276
- { if !self . mic_enabled { "Unmute" } else { "Mute" } }
234
+ <div id="main-container" >
235
+ <div id="grid-container" style={ if self . peer_list_open { "width: 80%;" } else { "width: 100%;" } } >
236
+ { self . error. as_ref( ) . map( |error| html! { <p>{ error } </p> } ) }
237
+ { rows }
238
+ {
239
+ if USERS_ALLOWED_TO_STREAM . iter( ) . any( |host| host == & email) || USERS_ALLOWED_TO_STREAM . is_empty( ) {
240
+ html! {
241
+ <nav class="host" >
242
+ <div class="controls" >
243
+ <button
244
+ class="bg-yew-blue p-2 rounded-md text-white"
245
+ onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleScreenShare ) } >
246
+ { if self . share_screen { "Stop Screen Share" } else { "Share Screen" } }
277
247
</button>
278
- </div>
279
- {
280
- if media_access_granted {
281
- html! { <Host client={ self . client. clone( ) } share_screen={ self . share_screen} mic_enabled={ self . mic_enabled} video_enabled={ self . video_enabled} />}
282
- } else {
283
- html! { <></>}
248
+ <button
249
+ class="bg-yew-blue p-2 rounded-md text-white"
250
+ onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleVideoOnOff ) } >
251
+ { if !self . video_enabled { "Start Video" } else { "Stop Video" } }
252
+ </button>
253
+ <button
254
+ class="bg-yew-blue p-2 rounded-md text-white"
255
+ onclick={ ctx. link( ) . callback( |_| MeetingAction :: ToggleMicMute ) } >
256
+ { if !self . mic_enabled { "Unmute" } else { "Mute" } }
257
+ </button>
258
+ <button
259
+ class="bg-yew-blue p-2 rounded-md text-white"
260
+ onclick={ toggle_peer_list. clone( ) } >
261
+ { if !self . peer_list_open { "Open Peers" } else { "Close Peers" } }
262
+ </button>
263
+ </div>
264
+ {
265
+ if media_access_granted {
266
+ html! { <Host client={ self . client. clone( ) } share_screen={ self . share_screen} mic_enabled={ self . mic_enabled} video_enabled={ self . video_enabled} />}
267
+ } else {
268
+ html! { <></>}
269
+ }
284
270
}
285
- }
286
- <h4 class="floating-name" >{ email} </h4>
271
+ <h4 class="floating-name" >{ email} </h4>
287
272
288
- { if !self . client. is_connected( ) {
289
- html! { <h4>{ "Connecting" } </h4>}
290
- } else {
291
- html! { <h4>{ "Connected" } </h4>}
292
- } }
273
+ { if !self . client. is_connected( ) {
274
+ html! { <h4>{ "Connecting" } </h4>}
275
+ } else {
276
+ html! { <h4>{ "Connected" } </h4>}
277
+ } }
293
278
294
- { if ctx. props( ) . e2ee_enabled {
295
- html! { <h4>{ "End to End Encryption Enabled" } </h4>}
296
- } else {
297
- html! { <h4>{ "End to End Encryption Disabled" } </h4>}
298
- } }
299
- </nav>
279
+ { if ctx. props( ) . e2ee_enabled {
280
+ html! { <h4>{ "End to End Encryption Enabled" } </h4>}
281
+ } else {
282
+ html! { <h4>{ "End to End Encryption Disabled" } </h4>}
283
+ } }
284
+ </nav>
285
+ }
286
+ } else {
287
+ error!( "User not allowed to stream" ) ;
288
+ error!( "allowed users {}" , USERS_ALLOWED_TO_STREAM . join( ", " ) ) ;
289
+ html! { }
300
290
}
301
- } else {
302
- error!( "User not allowed to stream" ) ;
303
- error!( "allowed users {}" , USERS_ALLOWED_TO_STREAM . join( ", " ) ) ;
304
- html! { }
305
291
}
306
- }
292
+ </div>
293
+ <div id="peer-list-container" class={ if self . peer_list_open { "visible" } else { "" } } >
294
+ <PeerList peers={ peers} onclose={ toggle_peer_list} />
295
+ </div>
307
296
</div>
308
297
}
309
298
}
310
299
}
311
-
312
- // props for the video component
313
- #[ derive( Properties , Debug , PartialEq ) ]
314
- pub struct UserVideoProps {
315
- pub id : String ,
316
- }
317
-
318
- // user video functional component
319
- #[ function_component( UserVideo ) ]
320
- fn user_video ( props : & UserVideoProps ) -> Html {
321
- // create use_effect hook that gets called only once and sets a thumbnail
322
- // for the user video
323
- let video_ref = use_state ( NodeRef :: default) ;
324
- let video_ref_clone = video_ref. clone ( ) ;
325
- use_effect_with_deps (
326
- move |_| {
327
- // Set thumbnail for the video
328
- let video = ( * video_ref_clone) . cast :: < HtmlCanvasElement > ( ) . unwrap ( ) ;
329
- let ctx = video
330
- . get_context ( "2d" )
331
- . unwrap ( )
332
- . unwrap ( )
333
- . unchecked_into :: < CanvasRenderingContext2d > ( ) ;
334
- ctx. clear_rect ( 0.0 , 0.0 , video. width ( ) as f64 , video. height ( ) as f64 ) ;
335
- || ( )
336
- } ,
337
- vec ! [ props. id. clone( ) ] ,
338
- ) ;
339
-
340
- html ! {
341
- <canvas ref={ ( * video_ref) . clone( ) } id={ props. id. clone( ) } ></canvas>
342
- }
343
- }
344
-
345
- fn toggle_pinned_div ( div_id : & str ) {
346
- if let Some ( div) = window ( )
347
- . and_then ( |w| w. document ( ) )
348
- . and_then ( |doc| doc. get_element_by_id ( div_id) )
349
- {
350
- // if the div does not have the grid-item-pinned css class, add it to it
351
- if !div. class_list ( ) . contains ( "grid-item-pinned" ) {
352
- div. class_list ( ) . add_1 ( "grid-item-pinned" ) . unwrap ( ) ;
353
- } else {
354
- // else remove it
355
- div. class_list ( ) . remove_1 ( "grid-item-pinned" ) . unwrap ( ) ;
356
- }
357
- }
358
- }
0 commit comments