Merge remote-tracking branch 'nordeck/nic/feat/configurable-whiteboard-widget'

This commit is contained in:
Travis Ralston 2020-12-28 20:38:55 -07:00
commit 0e90e73414
17 changed files with 180 additions and 3 deletions

View File

@ -0,0 +1,24 @@
import { QueryInterface } from "sequelize";
export default {
up: (queryInterface: QueryInterface) => {
return Promise.resolve()
.then(() => queryInterface.bulkInsert("dimension_widgets", [
{
type: "whiteboard",
name: "Whiteboard",
avatarUrl: "/img/avatars/whiteboard.png",
isEnabled: true,
isPublic: true,
description: "A whiteboard app embedded in the room.",
optionsJson: '{"defaultUrl":"https://cloud13.de/testwhiteboard/?whiteboardid=$roomId_$boardName"}',
}
]));
},
down: (queryInterface: QueryInterface) => {
return Promise.resolve()
.then(() => queryInterface.bulkDelete("dimension_widgets", {
type: "whiteboard",
}));
}
}

View File

@ -5,6 +5,10 @@ export interface EtherpadWidgetOptions {
defaultUrl: string;
}
export interface WhiteboardWidgetOptions {
defaultUrl: string;
}
export interface JitsiWidgetOptions {
jitsiDomain: string;
scriptUrl: string;

View File

@ -0,0 +1,22 @@
<div class="dialog">
<div class="dialog-header">
<h4>Whiteboard Widget Configuration</h4>
</div>
<div class="dialog-content">
<label class="label-block">
Default Board URL Template
<span class="text-muted ">$boardName and $roomId will be replaced during creation to help create a unique pad URL.</span>
<input type="text" class="form-control"
placeholder="https://cloud13.de/testwhiteboard/?whiteboardid=$roomId_$boardName"
[(ngModel)]="widget.options.defaultUrl" [disabled]="isUpdating"/>
</label>
</div>
<div class="dialog-footer">
<button type="button" (click)="save()" title="save" class="btn btn-primary btn-sm">
<i class="far fa-save"></i> Save
</button>
<button type="button" (click)="dialog.close()" title="close" class="btn btn-secondary btn-sm">
<i class="far fa-times-circle"></i> Cancel
</button>
</div>
</div>

View File

@ -0,0 +1,35 @@
import { Component } from "@angular/core";
import { FE_WhiteBoardWidget } from "../../../shared/models/integration";
import { ToasterService } from "angular2-toaster";
import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetConfigDialogContext } from "../widgets.component";
import { AdminIntegrationsApiService } from "../../../shared/services/admin/admin-integrations-api.service";
@Component({
templateUrl: "./whiteboard.component.html",
styleUrls: ["./whiteboard.component.scss", "../config-dialog.scss"],
})
export class AdminWidgetWhiteboardConfigComponent implements ModalComponent<WidgetConfigDialogContext> {
public isUpdating = false;
public widget: FE_WhiteBoardWidget;
private originalWidget: FE_WhiteBoardWidget;
constructor(public dialog: DialogRef<WidgetConfigDialogContext>, private adminIntegrationsApi: AdminIntegrationsApiService, private toaster: ToasterService) {
this.originalWidget = dialog.context.widget;
this.widget = JSON.parse(JSON.stringify(this.originalWidget));
}
public save() {
this.isUpdating = true;
this.adminIntegrationsApi.setIntegrationOptions(this.widget.category, this.widget.type, this.widget.options).then(() => {
this.originalWidget.options = this.widget.options;
this.toaster.pop("success", "Widget updated");
this.dialog.close();
}).catch(err => {
this.isUpdating = false;
console.error(err);
this.toaster.pop("error", "Error updating widget");
});
}
}

View File

@ -6,6 +6,7 @@ import { Modal, overlayConfigFactory } from "ngx-modialog";
import { BSModalContext } from "ngx-modialog/plugins/bootstrap";
import { AdminWidgetJitsiConfigComponent } from "./jitsi/jitsi.component";
import { AdminIntegrationsApiService } from "../../shared/services/admin/admin-integrations-api.service";
import { AdminWidgetWhiteboardConfigComponent } from "./whiteboard/whiteboard.component";
export class WidgetConfigDialogContext extends BSModalContext {
public widget: FE_Widget;
@ -50,6 +51,7 @@ export class AdminWidgetsComponent {
if (widget.type === "etherpad") component = AdminWidgetEtherpadConfigComponent;
if (widget.type === "jitsi") component = AdminWidgetJitsiConfigComponent;
if (widget.type === "whiteboard") component = AdminWidgetWhiteboardConfigComponent;
if (!component) {
console.error("No known dialog component for " + widget.type);
@ -67,6 +69,6 @@ export class AdminWidgetsComponent {
public hasConfiguration(widget: FE_Widget) {
// Currently only Jitsi and Etherpad have additional configuration
return widget.type === "jitsi" || widget.type === "etherpad";
return widget.type === "jitsi" || widget.type === "etherpad" || widget.type === "whiteboard";
}
}

View File

@ -121,6 +121,8 @@ import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.compo
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 { WhiteboardWidgetComponent } from "./configs/widget/whiteboard/whiteboard.widget.component";
import { AdminWidgetWhiteboardConfigComponent } from "./admin/widgets/whiteboard/whiteboard.component";
@NgModule({
imports: [
@ -220,6 +222,8 @@ import { BigBlueButtonApiService } from "./shared/services/integrations/bigblueb
AdminNewEditTermsComponent,
AdminTermsNewEditPublishDialogComponent,
TermsWidgetWrapperComponent,
WhiteboardWidgetComponent,
AdminWidgetWhiteboardConfigComponent
// Vendor
],
@ -277,6 +281,7 @@ import { BigBlueButtonApiService } from "./shared/services/integrations/bigblueb
AdminSlackBridgeManageSelfhostedComponent,
AdminLogoutConfirmationDialogComponent,
AdminTermsNewEditPublishDialogComponent,
AdminWidgetWhiteboardConfigComponent
]
})
export class AppModule {

View File

@ -49,6 +49,7 @@ import { ManagerTestWidgetWrapperComponent } from "./widget-wrappers/manager-tes
import { AdminTermsComponent } from "./admin/terms/terms.component";
import { AdminNewEditTermsComponent } from "./admin/terms/new-edit/new-edit.component";
import { TermsWidgetWrapperComponent } from "./widget-wrappers/terms/terms.component";
import { WhiteboardWidgetComponent } from "./configs/widget/whiteboard/whiteboard.widget.component";
const routes: Routes = [
{path: "", component: HomeComponent},
@ -232,6 +233,11 @@ const routes: Routes = [
component: SpotifyWidgetConfigComponent,
data: {breadcrumb: "Spotify Widgets", name: "Spotify Widgets"},
},
{
path: "whiteboard",
component: WhiteboardWidgetComponent,
data: {breadcrumb: "Whiteboard Widgets", name: "Whiteboard Widgets"},
},
],
},
{

View File

@ -0,0 +1,18 @@
<my-widget-config [widgetComponent]="this">
<ng-template #widgetParamsTemplate let-widget="widget">
<label class="label-block">
Whiteboard Name
<input type="text" class="form-control"
placeholder="{{ defaultName }}"
[(ngModel)]="widget.dimension.newName" name="widget-name-{{widget.id}}"
[disabled]="isUpdating"/>
</label>
<label class="label-block">
Whiteboard URL
<input type="text" class="form-control"
placeholder=""
[(ngModel)]="widget.dimension.newUrl" name="widget-url-{{widget.id}}"
[disabled]="isUpdating"/>
</label>
</ng-template>
</my-widget-config>

View File

@ -0,0 +1,46 @@
import { WidgetComponent } from "../widget.component";
import { Component } from "@angular/core";
import { EditableWidget, WIDGET_WHITEBOARD } from "../../../shared/models/widget";
import * as url from "url";
import { SessionStorage } from "../../../shared/SessionStorage";
import { NameService } from "../../../shared/services/name.service";
import { FE_WhiteBoardWidget } from "../../../shared/models/integration";
@Component({
templateUrl: "whiteboard.widget.component.html",
styleUrls: ["whiteboard.widget.component.scss"],
})
export class WhiteboardWidgetComponent extends WidgetComponent {
private whiteBoardWidget: FE_WhiteBoardWidget = <FE_WhiteBoardWidget>SessionStorage.editIntegration;
constructor(private nameService: NameService) {
super(WIDGET_WHITEBOARD, "Whiteboard", "generic", "whiteboard", "boardName");
}
protected OnWidgetsDiscovered(widgets: EditableWidget[]): void {
console.log(widgets);
for (const widget of widgets) {
if (!widget.dimension.newUrl.startsWith("http://") && !widget.dimension.newUrl.startsWith("https://")) {
const parsedUrl = url.parse(widget.url, true);
const boardName = parsedUrl.query["boardName"];
// Set the new URL so that it unpacks correctly
widget.url = `https://dev-whiteboard.nordeck.net/?whiteboardid=${boardName}`;
}
}
}
protected OnNewWidgetPrepared(widget: EditableWidget): void {
const name = this.nameService.getHumanReadableName();
let template = "https://dev-whiteboard.nordeck.net/?whiteboardid=$roomId_$boardName";
if (this.whiteBoardWidget.options && this.whiteBoardWidget.options.defaultUrl) {
template = this.whiteBoardWidget.options.defaultUrl;
}
template = template.replace("$roomId", encodeURIComponent(SessionStorage.roomId));
template = template.replace("$boardName", encodeURIComponent(name));
widget.dimension.newUrl = template;
widget.dimension.newName = name;
}
}

View File

@ -77,6 +77,10 @@
<img src="/img/avatars/customwidget.png">
<span>Custom Widget</span>
</div>
<div class="integration">
<img src="/img/avatars/whiteboard.png">
<span>Whiteboard</span>
</div>
</div>
</div>

View File

@ -99,6 +99,12 @@ export interface FE_BigBlueButtonWidget extends FE_Widget {
};
}
export interface FE_WhiteBoardWidget extends FE_Widget {
options: {
defaultUrl: string;
};
}
export interface FE_IntegrationRequirement {
condition: "publicRoom" | "canSendEventTypes" | "userInRoom";
argument: any;

View File

@ -12,6 +12,7 @@ export const WIDGET_TWITCH = ["twitch", "dimension-twitch"]; // TODO: m.* namesp
export const WIDGET_STICKER_PICKER = ["m.stickerpicker"];
export const WIDGET_TRADINGVIEW = ["tradingview", "dimension-tradingview"]; // TODO: Use m.tradingview (https://github.com/turt2live/matrix-dimension/issues/261)
export const WIDGET_SPOTIFY = ["m.spotify", "spotify", "dimension-spotify"];
export const WIDGET_WHITEBOARD = ["whiteboard", "phoenix-whiteboard"];
export interface EditableWidget {
/**

View File

@ -11,7 +11,8 @@ import {
WIDGET_STICKER_PICKER,
WIDGET_TRADINGVIEW,
WIDGET_TWITCH,
WIDGET_YOUTUBE
WIDGET_YOUTUBE,
WIDGET_WHITEBOARD
} from "../models/widget";
import { FE_Integration } from "../models/integration";
@ -69,6 +70,9 @@ export class IntegrationsRegistry {
"stickerpicker": {
types: WIDGET_STICKER_PICKER,
},
"whiteboard": {
type: WIDGET_WHITEBOARD,
}
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB