Add helpdesk icon and feature

Adds a "helpdesk" Jitsi room feature.
Can be enabled using the OpenSlides config page

Shows a 'Call support' button in the conference control bar
clicking the support button will connect the user
to a "support" jitsi room
The name of the support room will be
`JITSI_ROOM_NAME`-SUPPORT
This commit is contained in:
Sean 2021-01-28 15:30:49 +01:00
parent cc65b756c7
commit 024b9c74e6
3 changed files with 67 additions and 13 deletions

View File

@ -61,7 +61,7 @@
matTooltip="{{ 'Exit live conference and continue livestream' | translate }}" matTooltip="{{ 'Exit live conference and continue livestream' | translate }}"
*ngIf="videoStreamUrl && canSeeLiveStream && !isJitsiDialogOpen" *ngIf="videoStreamUrl && canSeeLiveStream && !isJitsiDialogOpen"
> >
<mat-icon color="warn"> meeting_room </mat-icon> <mat-icon color="warn">meeting_room</mat-icon>
</button> </button>
<!-- mute/unmute button --> <!-- mute/unmute button -->
@ -85,7 +85,7 @@
*ngIf="enableJitsi && isAccessPermitted" *ngIf="enableJitsi && isAccessPermitted"
class="quick-icon indicator" class="quick-icon indicator"
mat-mini-fab mat-mini-fab
(click)="enterConversation()" (click)="enterConferenceRoom()"
matTooltip="{{ 'Enter live conference' | translate }}" matTooltip="{{ 'Enter live conference' | translate }}"
> >
<mat-icon <mat-icon
@ -109,6 +109,18 @@
</a> </a>
</ng-container> </ng-container>
<!-- Call support button -->
<button
class="indicator quick-icon"
mat-mini-fab
(click)="enterSupportRoom()"
[disabled]="isJitsiActive"
matTooltip="{{ 'Access help desk' | translate }}"
*ngIf="canAccessSupport"
>
<mat-icon color="primary">live_help</mat-icon>
</button>
<!-- applause button --> <!-- applause button -->
<button <button
class="quick-icon indicator" class="quick-icon indicator"
@ -137,7 +149,8 @@
<!-- open-window button --> <!-- open-window button -->
<button class="toggle-list-button" mat-button (click)="toggleShowJitsi()"> <button class="toggle-list-button" mat-button (click)="toggleShowJitsi()">
<ng-container *ngIf="currentState == state.jitsi"> <ng-container *ngIf="currentState == state.jitsi">
<div class="ellipsis-overflow">{{ 'Live conference' | translate }}</div> <div *ngIf="!connectToHelpDesk" class="ellipsis-overflow">{{ 'Live conference' | translate }}</div>
<div *ngIf="connectToHelpDesk" class="ellipsis-overflow">{{ 'Help desk' | translate }}</div>
<div class="one-line"> <div class="one-line">
&nbsp; &nbsp;
<span *ngIf="currentDominantSpeaker"> <span *ngIf="currentDominantSpeaker">
@ -248,7 +261,7 @@
<button <button
mat-mini-fab mat-mini-fab
color="accent" color="accent"
(click)="enterConversation()" (click)="enterConferenceRoom()"
[disabled]=" [disabled]="
!enableJitsi || isJitsiActive || isJitsiActiveInAnotherTab || !isAccessPermitted !enableJitsi || isJitsiActive || isJitsiActiveInAnotherTab || !isAccessPermitted
" "

View File

@ -82,9 +82,13 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
public enableJitsi: boolean; public enableJitsi: boolean;
private autoconnect: boolean; private autoconnect: boolean;
private roomName: string; private defaultRoomName: string;
private actualRoomName: string;
private roomPassword: string; private roomPassword: string;
private jitsiDomain: string; private jitsiDomain: string;
private isSupportEnabled: boolean;
public connectToHelpDesk = false;
public restricted = false; public restricted = false;
public videoStreamUrl: string; public videoStreamUrl: string;
@ -139,6 +143,10 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
return this.roomPassword?.length > 0; return this.roomPassword?.length > 0;
} }
public get canAccessSupport(): boolean {
return this.isSupportEnabled && this.enableJitsi && !!this.defaultRoomName;
}
private isOnCurrentLos: boolean; private isOnCurrentLos: boolean;
public canSeeLiveStream: boolean; public canSeeLiveStream: boolean;
@ -174,7 +182,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
} }
public get jitsiMeetUrl(): string { public get jitsiMeetUrl(): string {
return `https://${this.jitsiDomain}/${this.roomName}`; return `https://${this.jitsiDomain}/${this.actualRoomName}`;
} }
/** /**
@ -339,7 +347,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
this.constantsService.get<JitsiSettings>('Settings').subscribe(settings => { this.constantsService.get<JitsiSettings>('Settings').subscribe(settings => {
if (settings) { if (settings) {
this.jitsiDomain = settings.JITSI_DOMAIN; this.jitsiDomain = settings.JITSI_DOMAIN;
this.roomName = settings.JITSI_ROOM_NAME; this.defaultRoomName = settings.JITSI_ROOM_NAME;
this.roomPassword = settings.JITSI_ROOM_PASSWORD; this.roomPassword = settings.JITSI_ROOM_PASSWORD;
this.constantsLoaded.resolve(); this.constantsLoaded.resolve();
} }
@ -352,7 +360,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
.get<boolean>('general_system_conference_auto_connect') .get<boolean>('general_system_conference_auto_connect')
.subscribe(autoconnect => (this.autoconnect = autoconnect)), .subscribe(autoconnect => (this.autoconnect = autoconnect)),
this.configService.get<boolean>('general_system_conference_show').subscribe(show => { this.configService.get<boolean>('general_system_conference_show').subscribe(show => {
this.enableJitsi = show && !!this.jitsiDomain && !!this.roomName; this.enableJitsi = show && !!this.jitsiDomain && !!this.defaultRoomName;
if (this.enableJitsi && this.autoconnect) { if (this.enableJitsi && this.autoconnect) {
this.startJitsi(); this.startJitsi();
} else { } else {
@ -392,6 +400,9 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
} else { } else {
this.isApplausBarUsed = false; this.isApplausBarUsed = false;
} }
}),
this.configService.get<boolean>('general_system_conference_enable_helpdesk').subscribe(enabled => {
this.isSupportEnabled = enabled;
}) })
); );
@ -425,19 +436,22 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
private startJitsi(): void { private startJitsi(): void {
if (!this.isJitsiActiveInAnotherTab && this.enableJitsi && !this.isJitsiActive && this.jitsiNode) { if (!this.isJitsiActiveInAnotherTab && this.enableJitsi && !this.isJitsiActive && this.jitsiNode) {
this.enterConversation(); this.enterConferenceRoom();
} }
} }
public async enterConversation(): Promise<void> { private async enterConversation(): Promise<void> {
await this.operator.loaded; await this.operator.loaded;
try { try {
await this.userMediaPermService.requestMediaAccess(); await this.userMediaPermService.requestMediaAccess();
this.storageMap.set(this.RTC_LOGGED_STORAGE_KEY, true).subscribe(() => {}); this.storageMap.set(this.RTC_LOGGED_STORAGE_KEY, true).subscribe(() => {});
this.setConferenceState(ConferenceState.jitsi); this.setConferenceState(ConferenceState.jitsi);
this.setOptions(); this.setOptions();
if (this.api) {
this.api.dispose();
this.api = undefined;
}
this.api = new JitsiMeetExternalAPI(this.jitsiDomain, this.options); this.api = new JitsiMeetExternalAPI(this.jitsiDomain, this.options);
const jitsiname = this.userRepo.getShortName(this.operator.user); const jitsiname = this.userRepo.getShortName(this.operator.user);
this.api.executeCommand('displayName', jitsiname); this.api.executeCommand('displayName', jitsiname);
this.loadApiCallbacks(); this.loadApiCallbacks();
@ -503,7 +517,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
operatorClosIndex <= this.nextSpeakerAmount && operatorClosIndex <= this.nextSpeakerAmount &&
!this.isJitsiActive !this.isJitsiActive
) { ) {
this.enterConversation(); this.enterConferenceRoom();
} }
} else { } else {
this.isOnCurrentLos = false; this.isOnCurrentLos = false;
@ -563,6 +577,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
} }
public async stopJitsi(): Promise<void> { public async stopJitsi(): Promise<void> {
this.connectToHelpDesk = false;
if (this.isJitsiActive) { if (this.isJitsiActive) {
this.api.executeCommand('hangup'); this.api.executeCommand('hangup');
this.clearMembers(); this.clearMembers();
@ -578,7 +593,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
private setOptions(): void { private setOptions(): void {
this.options = { this.options = {
roomName: this.roomName, roomName: this.actualRoomName,
parentNode: this.jitsiNode.nativeElement, parentNode: this.jitsiNode.nativeElement,
configOverwrite: this.configOverwrite, configOverwrite: this.configOverwrite,
interfaceConfigOverwrite: this.interfaceConfigOverwrite interfaceConfigOverwrite: this.interfaceConfigOverwrite
@ -617,6 +632,18 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit
this.storageMap.set(this.STREAM_RUNNING_STORAGE_KEY, true).subscribe(() => {}); this.storageMap.set(this.STREAM_RUNNING_STORAGE_KEY, true).subscribe(() => {});
} }
public enterConferenceRoom(): void {
this.actualRoomName = this.defaultRoomName;
this.connectToHelpDesk = false;
this.enterConversation();
}
public enterSupportRoom(): void {
this.actualRoomName = `${this.defaultRoomName}-SUPPORT`;
this.connectToHelpDesk = true;
this.enterConversation();
}
private onLiveStreamAvailable(liveStreamUrl: string): void { private onLiveStreamAvailable(liveStreamUrl: string): void {
this.videoStreamUrl = liveStreamUrl; this.videoStreamUrl = liveStreamUrl;
// this is the "dead" state; you would see the jitsi state; but are not connected // this is the "dead" state; you would see the jitsi state; but are not connected

View File

@ -169,6 +169,20 @@ def get_config_variables():
subgroup="Live conference", subgroup="Live conference",
) )
yield ConfigVariable(
name="general_system_conference_enable_helpdesk",
default_value=False,
input_type="boolean",
label="Enable help desk",
help_text="""
Shows a help icon in the conference bar.
Users can connect to a dedicated conference.
The conference host has to manually ensure the coverage of the help desk.
""",
weight=148,
subgroup="Live conference",
)
# Applause # Applause
yield ConfigVariable( yield ConfigVariable(