mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-07-19 17:31:58 +00:00
Add the client-side widget code
Here is where the actual code that runs in the widget's iframe is. This includes the HTML/CSS stuff, the definitions for API request/responses, some routing and the javascript which makes requests to the new /join api endpoint.
This commit is contained in:
parent
8041c07a68
commit
e3f27156e0
|
@ -119,6 +119,7 @@ import { AdminNewEditTermsComponent } from "./admin/terms/new-edit/new-edit.comp
|
||||||
import { AdminTermsNewEditPublishDialogComponent } from "./admin/terms/new-edit/publish/publish.component";
|
import { AdminTermsNewEditPublishDialogComponent } from "./admin/terms/new-edit/publish/publish.component";
|
||||||
import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.component";
|
import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.component";
|
||||||
import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component";
|
import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component";
|
||||||
|
import { BigBlueButtonWidgetWrapperComponent } from "./widget-wrappers/bigbluebutton/bigbluebutton.component";
|
||||||
import { BigBlueButtonApiService } from "./shared/services/integrations/bigbluebutton-api.service";
|
import { BigBlueButtonApiService } from "./shared/services/integrations/bigbluebutton-api.service";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -149,6 +150,7 @@ import { BigBlueButtonApiService } from "./shared/services/integrations/bigblueb
|
||||||
FullscreenButtonComponent,
|
FullscreenButtonComponent,
|
||||||
VideoWidgetWrapperComponent,
|
VideoWidgetWrapperComponent,
|
||||||
JitsiWidgetWrapperComponent,
|
JitsiWidgetWrapperComponent,
|
||||||
|
BigBlueButtonWidgetWrapperComponent,
|
||||||
GCalWidgetWrapperComponent,
|
GCalWidgetWrapperComponent,
|
||||||
BigBlueButtonConfigComponent,
|
BigBlueButtonConfigComponent,
|
||||||
RiotHomeComponent,
|
RiotHomeComponent,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { RouterModule, Routes } from "@angular/router";
|
||||||
import { HomeComponent } from "./home/home.component";
|
import { HomeComponent } from "./home/home.component";
|
||||||
import { RiotComponent } from "./riot/riot.component";
|
import { RiotComponent } from "./riot/riot.component";
|
||||||
import { GenericWidgetWrapperComponent } from "./widget-wrappers/generic/generic.component";
|
import { GenericWidgetWrapperComponent } from "./widget-wrappers/generic/generic.component";
|
||||||
|
import { BigBlueButtonWidgetWrapperComponent } from "./widget-wrappers/bigbluebutton/bigbluebutton.component";
|
||||||
import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component";
|
import { BigBlueButtonConfigComponent } from "./configs/widget/bigbluebutton/bigbluebutton.widget.component";
|
||||||
import { VideoWidgetWrapperComponent } from "./widget-wrappers/video/video.component";
|
import { VideoWidgetWrapperComponent } from "./widget-wrappers/video/video.component";
|
||||||
import { JitsiWidgetWrapperComponent } from "./widget-wrappers/jitsi/jitsi.component";
|
import { JitsiWidgetWrapperComponent } from "./widget-wrappers/jitsi/jitsi.component";
|
||||||
|
@ -292,6 +293,7 @@ const routes: Routes = [
|
||||||
{path: "generic", component: GenericWidgetWrapperComponent},
|
{path: "generic", component: GenericWidgetWrapperComponent},
|
||||||
{path: "video", component: VideoWidgetWrapperComponent},
|
{path: "video", component: VideoWidgetWrapperComponent},
|
||||||
{path: "jitsi", component: JitsiWidgetWrapperComponent},
|
{path: "jitsi", component: JitsiWidgetWrapperComponent},
|
||||||
|
{path: "bigbluebutton", component: BigBlueButtonWidgetWrapperComponent},
|
||||||
{path: "gcal", component: GCalWidgetWrapperComponent},
|
{path: "gcal", component: GCalWidgetWrapperComponent},
|
||||||
{path: "stickerpicker", component: StickerPickerWidgetWrapperComponent},
|
{path: "stickerpicker", component: StickerPickerWidgetWrapperComponent},
|
||||||
{path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent},
|
{path: "generic-fullscreen", component: GenericFullscreenWidgetWrapperComponent},
|
||||||
|
|
|
@ -64,6 +64,11 @@ export interface FE_Sticker {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FE_BigBlueButtonJoin {
|
||||||
|
// The meeting URL the client should load to join the meeting
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface FE_StickerConfig {
|
export interface FE_StickerConfig {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
stickerBot: string;
|
stickerBot: string;
|
||||||
|
@ -88,8 +93,14 @@ export interface FE_JitsiWidget extends FE_Widget {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FE_BigBlueButtonWidget extends FE_Widget {
|
||||||
|
options: {
|
||||||
|
conferenceUrl: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export interface FE_IntegrationRequirement {
|
export interface FE_IntegrationRequirement {
|
||||||
condition: "publicRoom" | "canSendEventTypes" | "userInRoom";
|
condition: "publicRoom" | "canSendEventTypes" | "userInRoom";
|
||||||
argument: any;
|
argument: any;
|
||||||
expectedValue: any;
|
expectedValue: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { AuthedApi } from "../authed-api";
|
||||||
|
import { FE_BigBlueButtonJoin } from "../../models/integration"
|
||||||
|
import { HttpClient } from "@angular/common/http";
|
||||||
|
import { ApiError } from "../../../../../src/api/ApiError";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BigBlueButtonApiService extends AuthedApi {
|
||||||
|
constructor(http: HttpClient) {
|
||||||
|
super(http);
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinMeeting(url: string, name: string): Promise<FE_BigBlueButtonJoin|ApiError> {
|
||||||
|
return this.authedGet<FE_BigBlueButtonJoin|ApiError>("/api/v1/dimension/bigbluebutton/join", {greenlightUrl: url, fullName: name}).toPromise();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<iframe *ngIf="embedUrl"
|
||||||
|
id="bigBlueButtonContainer"
|
||||||
|
[src]="embedUrl"
|
||||||
|
(load)="onIframeLoad()"
|
||||||
|
frameborder="0"
|
||||||
|
allowfullscreen
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
></iframe>
|
||||||
|
|
||||||
|
<div *ngIf="!embedUrl" class="join-conference-wrapper">
|
||||||
|
<div class="join-conference-boat">
|
||||||
|
<div *ngIf="statusMessage; else joinMeetingPrompt" class="join-conference-prompt">
|
||||||
|
<h4 [innerHTML]="statusMessage"></h4>
|
||||||
|
</div>
|
||||||
|
<ng-template #joinMeetingPrompt>
|
||||||
|
<div class="join-conference-prompt">
|
||||||
|
<h3>BigBlueButton Conference</h3>
|
||||||
|
<button type="button" (click)="joinConference()" class="btn btn-primary btn-large">
|
||||||
|
Join Conference
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
// component styles are encapsulated and only applied to their components
|
||||||
|
@import "../../../style/themes/themes";
|
||||||
|
|
||||||
|
@include themifyComponent() {
|
||||||
|
#bigBlueButtonContainer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-conference-wrapper {
|
||||||
|
display: table;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: themed(widgetWelcomeBgColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-conference-boat {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.join-conference-prompt {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 90%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
152
web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.ts
Normal file
152
web/app/widget-wrappers/bigbluebutton/bigbluebutton.component.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { WidgetApiService } from "../../shared/services/integrations/widget-api.service";
|
||||||
|
import { Subscription } from "rxjs/Subscription";
|
||||||
|
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
|
||||||
|
import { CapableWidget } from "../capable-widget";
|
||||||
|
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||||
|
import { BigBlueButtonApiService } from "../../shared/services/integrations/bigbluebutton-api.service";
|
||||||
|
import { FE_BigBlueButtonJoin } from "../../shared/models/integration";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "my-bigbluebutton-widget-wrapper",
|
||||||
|
templateUrl: "bigbluebutton.component.html",
|
||||||
|
styleUrls: ["bigbluebutton.component.scss"],
|
||||||
|
})
|
||||||
|
export class BigBlueButtonWidgetWrapperComponent extends CapableWidget implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
public canEmbed = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User metadata passed to us by the client
|
||||||
|
*/
|
||||||
|
private conferenceUrl: string;
|
||||||
|
private displayName: string;
|
||||||
|
private userId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The poll period in ms while waiting for a meeting to start
|
||||||
|
*/
|
||||||
|
private pollIntervalMillis = 5000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber for messages from the client via the postMessage API
|
||||||
|
*/
|
||||||
|
private bigBlueButtonApiSubscription: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A status message to display to the user in the widget, typically for loading messages
|
||||||
|
*/
|
||||||
|
public statusMessage: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether we are currently in a meeting
|
||||||
|
*/
|
||||||
|
private inMeeting: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL to embed into the iframe
|
||||||
|
*/
|
||||||
|
public embedUrl: SafeUrl = null;
|
||||||
|
|
||||||
|
constructor(activatedRoute: ActivatedRoute,
|
||||||
|
private bigBlueButtonApi: BigBlueButtonApiService,
|
||||||
|
private widgetApi: WidgetApiService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
|
super();
|
||||||
|
this.supportsAlwaysOnScreen = true;
|
||||||
|
|
||||||
|
let params: any = activatedRoute.snapshot.queryParams;
|
||||||
|
|
||||||
|
console.log("BigBlueButton: Given greenlight url: " + params.conferenceUrl);
|
||||||
|
|
||||||
|
this.conferenceUrl = params.conferenceUrl;
|
||||||
|
this.displayName = params.displayName;
|
||||||
|
this.userId = params.userId || params.email; // Element uses `email` when placing a conference call
|
||||||
|
|
||||||
|
// Set the widget ID if we have it
|
||||||
|
ScalarWidgetApi.widgetId = params.widgetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit() {
|
||||||
|
super.ngOnInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onIframeLoad() {
|
||||||
|
if (this.inMeeting) {
|
||||||
|
// The meeting has ended and we've come back full circle
|
||||||
|
this.inMeeting = false;
|
||||||
|
this.statusMessage = null;
|
||||||
|
this.embedUrl = null;
|
||||||
|
|
||||||
|
ScalarWidgetApi.sendSetAlwaysOnScreen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Have a toggle for whether we're in a meeting. We do this as we don't have a method
|
||||||
|
// of checking which URL was just loaded in the iframe (due to different origin domains
|
||||||
|
// and browser security), so we have to guess that it'll always be the second load (the
|
||||||
|
// first being joining the meeting)
|
||||||
|
this.inMeeting = true;
|
||||||
|
|
||||||
|
// We've successfully joined the meeting
|
||||||
|
ScalarWidgetApi.sendSetAlwaysOnScreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinConference(updateStatusMessage: boolean = true) {
|
||||||
|
if (updateStatusMessage) {
|
||||||
|
// Inform the user that we're loading their meeting
|
||||||
|
this.statusMessage = "Joining conference...";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a nick to display in the meeting
|
||||||
|
const joinName = `${this.displayName} (${this.userId})`;
|
||||||
|
|
||||||
|
// Make a request to Dimension requesting the join URL
|
||||||
|
console.log("BigBlueButton: joining via greenlight url:", this.conferenceUrl);
|
||||||
|
this.bigBlueButtonApi.joinMeeting(this.conferenceUrl, joinName).then((response) => {
|
||||||
|
if ("errorCode" in response) {
|
||||||
|
// This is an instance of ApiError
|
||||||
|
if (response.errorCode == "WAITING_FOR_MEETING_START") {
|
||||||
|
// The meeting hasn't started yet
|
||||||
|
this.statusMessage = "Waiting for conference to start...";
|
||||||
|
|
||||||
|
// Poll until it has
|
||||||
|
setTimeout(this.joinConference.bind(this), this.pollIntervalMillis, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise this is a generic error
|
||||||
|
this.statusMessage = "An error occurred while loading the meeting";
|
||||||
|
}
|
||||||
|
|
||||||
|
const joinUrl = (response as FE_BigBlueButtonJoin).url;
|
||||||
|
|
||||||
|
// Check if the given URL is embeddable
|
||||||
|
this.widgetApi.isEmbeddable(joinUrl).then(result => {
|
||||||
|
this.canEmbed = result.canEmbed;
|
||||||
|
this.statusMessage = null;
|
||||||
|
|
||||||
|
// Embed the return meeting URL, joining the meeting
|
||||||
|
this.embedUrl = this.sanitizer.bypassSecurityTrustResourceUrl(joinUrl);
|
||||||
|
|
||||||
|
// Inform the client that we would like the meeting to remain visible for its duration
|
||||||
|
ScalarWidgetApi.sendSetAlwaysOnScreen(true);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.canEmbed = false;
|
||||||
|
this.statusMessage = "Unable to embed meeting";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
if (this.bigBlueButtonApiSubscription) this.bigBlueButtonApiSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onCapabilitiesSent(): void {
|
||||||
|
super.onCapabilitiesSent();
|
||||||
|
ScalarWidgetApi.sendSetAlwaysOnScreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user