openvidu-componentS: Decoupled panels from openvidu layout

pull/690/head
csantosm 2022-01-26 16:01:12 +01:00
parent 9f9f09c8c5
commit e16816422c
6 changed files with 176 additions and 183 deletions

View File

@ -2,34 +2,6 @@
height: 100%; height: 100%;
} }
.sidenav-container {
position: relative;
width: 100%;
height: 100%;
min-height: -webkit-fill-available;
overflow: hidden;
}
.sidenav-menu {
display: flex;
align-items: center;
justify-content: center;
width: 360px;
background-color: var(--ov-primary-color);
border-left: none;
position: absolute;
}
.mat-drawer-container {
background-color: var(--ov-primary-color);
}
.sidenav-main {
height: 100%;
overflow: hidden;
min-height: -webkit-fill-available;
min-height: -moz-available;
}
.bounds { .bounds {
position: absolute; position: absolute;

View File

@ -1,29 +1,4 @@
<mat-sidenav-container class="sidenav-container">
<mat-sidenav
#sidenav
mode="{{ sidenavMode }}"
position="end"
class="sidenav-menu"
fixedInViewport="true"
fixedTopGap="0"
fixedBottomGap="0"
>
<!-- Custom menu content -->
<ng-container *ngIf="customMenuContentTemplate; else defaultMenuContent">
<ng-container *ngTemplateOutlet="customMenuContentTemplate"></ng-container>
</ng-container>
<!-- Default menu content if custom menu content is not injected -->
<ng-template #defaultMenuContent>
<ov-chat-panel *ngIf="isChatOpened"></ov-chat-panel>
<ov-participants-panel *ngIf="isParticipantsOpened"></ov-participants-panel>
</ng-template>
</mat-sidenav>
<mat-sidenav-content class="sidenav-main">
<div id="layout" class="bounds"> <div id="layout" class="bounds">
<!-- Custom local participant --> <!-- Custom local participant -->
<ng-container *ngIf="customLocalParticipantTemplate; else defaultLocalParticipant"> <ng-container *ngIf="customLocalParticipantTemplate; else defaultLocalParticipant">
<ng-container *ngTemplateOutlet="customLocalParticipantTemplate"></ng-container> <ng-container *ngTemplateOutlet="customLocalParticipantTemplate"></ng-container>
@ -41,11 +16,6 @@
</div> </div>
</ng-template> </ng-template>
<!-- Custom layout elements -->
<ng-container *ngIf="customLayoutElementTemplate">
<ng-container *ngTemplateOutlet="customLayoutElementTemplate" [@inOutAnimation]></ng-container>
</ng-container>
<!-- Custom remote participant --> <!-- Custom remote participant -->
<ng-container *ngIf="customRemoteParticipantsTemplate; else defaultRemoteParticipants"> <ng-container *ngIf="customRemoteParticipantsTemplate; else defaultRemoteParticipants">
<ng-container *ngTemplateOutlet="customRemoteParticipantsTemplate"></ng-container> <ng-container *ngTemplateOutlet="customRemoteParticipantsTemplate"></ng-container>
@ -63,5 +33,3 @@
</div> </div>
</ng-template> </ng-template>
</div> </div>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@ -1,12 +1,8 @@
import { AfterViewInit, Component, ContentChild, HostListener, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { AfterViewInit, Component, ContentChild, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav'; import { Subscription } from 'rxjs';
import { skip, Subscription } from 'rxjs';
import { SidenavMode } from '../../models/layout.model';
import { LayoutService } from '../../services/layout/layout.service';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
import { ParticipantAbstractModel } from '../../models/participant.model'; import { ParticipantAbstractModel } from '../../models/participant.model';
import { MenuType } from '../../models/menu.model'; import { LayoutService } from '../../services/layout/layout.service';
@Component({ @Component({
selector: 'ov-layout', selector: 'ov-layout',
@ -16,96 +12,28 @@ import { MenuType } from '../../models/menu.model';
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit { export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
@ContentChild('customLocalParticipant', { read: TemplateRef }) customLocalParticipantTemplate: TemplateRef<any>; @ContentChild('customLocalParticipant', { read: TemplateRef }) customLocalParticipantTemplate: TemplateRef<any>;
@ContentChild('customRemoteParticipants', { read: TemplateRef }) customRemoteParticipantsTemplate: TemplateRef<any>; @ContentChild('customRemoteParticipants', { read: TemplateRef }) customRemoteParticipantsTemplate: TemplateRef<any>;
@ContentChild('customMenuContent', { read: TemplateRef }) customMenuContentTemplate: TemplateRef<any>;
@ContentChild('customLayoutElement', { read: TemplateRef }) customLayoutElementTemplate: TemplateRef<any>;
showCustomParticipant = true;
sideMenu: MatSidenav;
localParticipant: ParticipantAbstractModel; localParticipant: ParticipantAbstractModel;
remoteParticipants: ParticipantAbstractModel[] = []; remoteParticipants: ParticipantAbstractModel[] = [];
sidenavMode: SidenavMode = SidenavMode.SIDE;
isParticipantsOpened: boolean;
isChatOpened: boolean;
protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
protected menuSubscription: Subscription;
protected layoutWidthSubscription: Subscription;
protected localParticipantSubs: Subscription; protected localParticipantSubs: Subscription;
protected remoteParticipantsSubs: Subscription; protected remoteParticipantsSubs: Subscription;
protected updateLayoutInterval: NodeJS.Timer; protected updateLayoutInterval: NodeJS.Timer;
@HostListener('window:resize') constructor(protected layoutService: LayoutService, protected participantService: ParticipantService) {}
sizeChange() {
this.layoutService.update();
}
constructor(
protected participantService: ParticipantService,
protected layoutService: LayoutService,
protected menuService: SidenavMenuService
) {}
@ViewChild('sidenav')
set sidenavMenu(menu: MatSidenav) {
setTimeout(() => {
if (menu) {
this.sideMenu = menu;
this.subscribeToTogglingMenu();
}
}, 0);
}
ngOnInit(): void { ngOnInit(): void {
this.layoutService.initialize();
this.subscribeToLayoutWidth();
this.subscribeToUsers(); this.subscribeToUsers();
} }
ngAfterViewInit() {} ngAfterViewInit() {}
ngOnDestroy() { ngOnDestroy() {
this.layoutService.clear();
this.localParticipant = null; this.localParticipant = null;
this.remoteParticipants = []; this.remoteParticipants = [];
this.isChatOpened = false;
this.isParticipantsOpened = false;
if (this.menuSubscription) this.menuSubscription.unsubscribe();
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe(); if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe(); if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe();
} }
protected subscribeToTogglingMenu() {
this.sideMenu.openedChange.subscribe(() => {
if(this.updateLayoutInterval) {
clearInterval(this.updateLayoutInterval);
}
this.layoutService.update();
});
this.sideMenu.openedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50);
});
this.sideMenu.closedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50);
});
this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => {
if (this.sideMenu) {
this.isChatOpened = ev.opened && ev.type === MenuType.CHAT;
this.isParticipantsOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
ev.opened ? this.sideMenu.open() : this.sideMenu.close();
}
});
}
protected subscribeToLayoutWidth() {
this.layoutWidthSubscription = this.layoutService.layoutWidthObs.subscribe((width) => {
this.sidenavMode = width <= this.SIDENAV_WIDTH_LIMIT_MODE ? SidenavMode.OVER : SidenavMode.SIDE;
});
}
protected subscribeToUsers() { protected subscribeToUsers() {
this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p) => { this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p) => {
this.localParticipant = p; this.localParticipant = p;

View File

@ -4,6 +4,36 @@
height: 100%; height: 100%;
} }
.sidenav-container {
position: relative;
height: calc(100% - 80px);
min-height: calc(100% - 80px);
padding-top: 10px;
width: 100%;
overflow: hidden;
}
.sidenav-menu {
display: flex;
align-items: center;
justify-content: center;
width: 360px;
background-color: var(--ov-primary-color);
border-left: none;
position: absolute;
}
.sidenav-main {
height: 100%;
overflow: hidden;
min-height: -webkit-fill-available;
min-height: -moz-available;
}
.mat-drawer-container {
background-color: var(--ov-primary-color);
}
#toolbar-container, #footer-container { #toolbar-container, #footer-container {
background-color: var(--ov-primary-color); background-color: var(--ov-primary-color);
min-width: 400px !important; min-width: 400px !important;
@ -24,11 +54,6 @@
} }
#layout-container {
height: calc(100% - 80px);
padding-top: 10px;
}
.reconnecting-container{ .reconnecting-container{
width: 100%; width: 100%;

View File

@ -1,4 +1,34 @@
<div id="session-container"> <div id="session-container">
<mat-sidenav-container class="sidenav-container">
<!-- OPENVIDU PANELS -->
<mat-sidenav
#sidenav
mode="{{ sidenavMode }}"
position="end"
class="sidenav-menu"
fixedInViewport="true"
fixedTopGap="0"
fixedBottomGap="0"
>
<!-- Custom menu content -->
<ng-container *ngIf="customPanelContentTemplate; else defaultPanelContent">
<ng-container *ngTemplateOutlet="customPanelContentTemplate"></ng-container>
</ng-container>
<!-- Default menu content if custom menu content is not injected -->
<ng-template #defaultPanelContent>
<ov-chat-panel *ngIf="isChatPanelOpened"></ov-chat-panel>
<ov-participants-panel *ngIf="isParticipantsPanelOpened"></ov-participants-panel>
</ng-template>
</mat-sidenav>
<!-- OPENVIDU LAYOUT -->
<mat-sidenav-content class="sidenav-main">
<div id="layout-container">
<ng-content select="[layout]"></ng-content>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
<!-- Custom toolbar --> <!-- Custom toolbar -->
<!-- <ng-container *ngIf="toolbarTemplate"> <!-- <ng-container *ngIf="toolbarTemplate">
@ -7,15 +37,9 @@
</div> </div>
</ng-container> --> </ng-container> -->
<div id="layout-container">
<ng-content select="[layout]"></ng-content>
</div>
<ng-container *ngIf="toolbarTemplate"> <ng-container *ngIf="toolbarTemplate">
<div id="footer-container"> <div id="footer-container">
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container> <ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div> </div>
</ng-container> </ng-container>
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, ContentChild, EventEmitter, HostListener, Input, OnInit, Output, TemplateRef } from '@angular/core'; import { Component, ContentChild, EventEmitter, HostListener, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core';
import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser'; import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser';
import { VideoType } from '../../models/video-type.model'; import { VideoType } from '../../models/video-type.model';
@ -12,6 +12,12 @@ import { PlatformService } from '../../services/platform/platform.service';
import { ActionService } from '../../services/action/action.service'; import { ActionService } from '../../services/action/action.service';
import { Signal } from '../../models/signal.model'; import { Signal } from '../../models/signal.model';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
import { MatSidenav } from '@angular/material/sidenav';
import { SidenavMode } from '../../models/layout.model';
import { LayoutService } from '../../services/layout/layout.service';
import { Subscription, skip } from 'rxjs';
import { MenuType } from '../../models/menu.model';
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
@Component({ @Component({
selector: 'ov-session', selector: 'ov-session',
@ -20,6 +26,9 @@ import { ParticipantService } from '../../services/participant/participant.servi
}) })
export class SessionComponent implements OnInit { export class SessionComponent implements OnInit {
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>; @ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
@ContentChild('customPanelContent', { read: TemplateRef }) customPanelContentTemplate: TemplateRef<any>;
@ContentChild('customLayoutElement', { read: TemplateRef }) customLayoutElementTemplate: TemplateRef<any>;
@Input() tokens: { webcam: string; screen: string }; @Input() tokens: { webcam: string; screen: string };
@Output() _session = new EventEmitter<any>(); @Output() _session = new EventEmitter<any>();
@Output() _publisher = new EventEmitter<any>(); @Output() _publisher = new EventEmitter<any>();
@ -27,6 +36,19 @@ export class SessionComponent implements OnInit {
session: Session; session: Session;
sessionScreen: Session; sessionScreen: Session;
sideMenu: MatSidenav;
sidenavMode: SidenavMode = SidenavMode.SIDE;
isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean;
protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
protected menuSubscription: Subscription;
protected layoutWidthSubscription: Subscription;
protected updateLayoutInterval: NodeJS.Timer;
protected log: ILogger; protected log: ILogger;
constructor( constructor(
@ -36,6 +58,9 @@ export class SessionComponent implements OnInit {
protected loggerSrv: LoggerService, protected loggerSrv: LoggerService,
protected chatService: ChatService, protected chatService: ChatService,
protected tokenService: TokenService, protected tokenService: TokenService,
protected layoutService: LayoutService,
protected menuService: SidenavMenuService,
protected platformService: PlatformService protected platformService: PlatformService
) { ) {
this.log = this.loggerSrv.get('SessionComponent'); this.log = this.loggerSrv.get('SessionComponent');
@ -46,7 +71,24 @@ export class SessionComponent implements OnInit {
this.leaveSession(); this.leaveSession();
} }
@HostListener('window:resize')
sizeChange() {
this.layoutService.update();
}
@ViewChild('sidenav')
set sidenavMenu(menu: MatSidenav) {
setTimeout(() => {
if (menu) {
this.sideMenu = menu;
this.subscribeToTogglingMenu();
}
}, 0);
}
async ngOnInit() { async ngOnInit() {
this.layoutService.initialize();
if (this.webrtcService.getWebcamSession() === null) { if (this.webrtcService.getWebcamSession() === null) {
this.webrtcService.initialize(); this.webrtcService.initialize();
await this.webrtcService.initDefaultPublisher(undefined); await this.webrtcService.initDefaultPublisher(undefined);
@ -81,6 +123,11 @@ export class SessionComponent implements OnInit {
this.participantService.clear(); this.participantService.clear();
this.session = null; this.session = null;
this.sessionScreen = null; this.sessionScreen = null;
this.layoutService.clear();
this.isChatPanelOpened = false;
this.isParticipantsPanelOpened = false;
if (this.menuSubscription) this.menuSubscription.unsubscribe();
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
} }
leaveSession() { leaveSession() {
@ -88,6 +135,37 @@ export class SessionComponent implements OnInit {
this.webrtcService.disconnect(); this.webrtcService.disconnect();
} }
protected subscribeToTogglingMenu() {
this.sideMenu.openedChange.subscribe(() => {
if (this.updateLayoutInterval) {
clearInterval(this.updateLayoutInterval);
}
this.layoutService.update();
});
this.sideMenu.openedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50);
});
this.sideMenu.closedStart.subscribe(() => {
this.updateLayoutInterval = setInterval(() => this.layoutService.update(), 50);
});
this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => {
if (this.sideMenu) {
this.isChatPanelOpened = ev.opened && ev.type === MenuType.CHAT;
this.isParticipantsPanelOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
ev.opened ? this.sideMenu.open() : this.sideMenu.close();
}
});
}
protected subscribeToLayoutWidth() {
this.layoutWidthSubscription = this.layoutService.layoutWidthObs.subscribe((width) => {
this.sidenavMode = width <= this.SIDENAV_WIDTH_LIMIT_MODE ? SidenavMode.OVER : SidenavMode.SIDE;
});
}
private async connectToSession(): Promise<void> { private async connectToSession(): Promise<void> {
try { try {
if (this.participantService.areBothEnabled()) { if (this.participantService.areBothEnabled()) {
@ -117,7 +195,6 @@ export class SessionComponent implements OnInit {
const isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`); const isCameraConnection: boolean = !nickname?.includes(`_${VideoType.SCREEN}`);
const data = event.connection?.data; const data = event.connection?.data;
if (isRemoteConnection && isCameraConnection) { if (isRemoteConnection && isCameraConnection) {
// Adding participant when connection is created and it's not screen // Adding participant when connection is created and it's not screen
this.participantService.addRemoteConnection(connectionId, data, null); this.participantService.addRemoteConnection(connectionId, data, null);
@ -167,7 +244,6 @@ export class SessionComponent implements OnInit {
// this.session.on('streamPropertyChanged', (event: StreamPropertyChangedEvent) => { // this.session.on('streamPropertyChanged', (event: StreamPropertyChangedEvent) => {
// const connectionId = event.stream.connection.connectionId; // const connectionId = event.stream.connection.connectionId;
// const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId); // const isRemoteConnection: boolean = !this.webrtcService.isMyOwnConnection(connectionId);
// if (isRemoteConnection) { // if (isRemoteConnection) {
// if (event.changedProperty === 'videoActive') { // if (event.changedProperty === 'videoActive') {
// // this.participantService.updateUsers(); // // this.participantService.updateUsers();