Add etherpad widget configuration

This commit is contained in:
Travis Ralston 2017-12-23 13:16:05 -07:00
parent 7fd674a8ff
commit fce6f2174a
7 changed files with 183 additions and 26 deletions

View File

@ -33,6 +33,8 @@ import { ServiceLocator } from "./shared/services/locator.service";
import { IboxComponent } from "./elements/ibox/ibox.component"; import { IboxComponent } from "./elements/ibox/ibox.component";
import { CustomWidgetConfigComponent } from "./configs/widget/custom/custom.widget.component"; import { CustomWidgetConfigComponent } from "./configs/widget/custom/custom.widget.component";
import { ConfigScreenWidgetComponent } from "./configs/widget/config_screen/config_screen.widget.component"; import { ConfigScreenWidgetComponent } from "./configs/widget/config_screen/config_screen.widget.component";
import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpad.widget.component";
import { NameService } from "./shared/services/name.service";
@NgModule({ @NgModule({
imports: [ imports: [
@ -66,6 +68,7 @@ import { ConfigScreenWidgetComponent } from "./configs/widget/config_screen/conf
IboxComponent, IboxComponent,
ConfigScreenWidgetComponent, ConfigScreenWidgetComponent,
CustomWidgetConfigComponent, CustomWidgetConfigComponent,
EtherpadWidgetConfigComponent,
// Vendor // Vendor
], ],
@ -74,6 +77,7 @@ import { ConfigScreenWidgetComponent } from "./configs/widget/config_screen/conf
ScalarServerApiService, ScalarServerApiService,
DimensionApiService, DimensionApiService,
AdminApiService, AdminApiService,
NameService,
{provide: Window, useValue: window}, {provide: Window, useValue: window},
// Vendor // Vendor

View File

@ -7,6 +7,7 @@ import { JitsiWidgetWrapperComponent } from "./widget_wrappers/jitsi/jitsi.compo
import { GCalWidgetWrapperComponent } from "./widget_wrappers/gcal/gcal.component"; import { GCalWidgetWrapperComponent } from "./widget_wrappers/gcal/gcal.component";
import { RiotHomeComponent } from "./riot/riot-home/home.component"; import { RiotHomeComponent } from "./riot/riot-home/home.component";
import { CustomWidgetConfigComponent } from "./configs/widget/custom/custom.widget.component"; import { CustomWidgetConfigComponent } from "./configs/widget/custom/custom.widget.component";
import { EtherpadWidgetConfigComponent } from "./configs/widget/etherpad/etherpad.widget.component";
const routes: Routes = [ const routes: Routes = [
{path: "", component: HomeComponent}, {path: "", component: HomeComponent},
@ -28,6 +29,11 @@ const routes: Routes = [
component: CustomWidgetConfigComponent, component: CustomWidgetConfigComponent,
data: {breadcrumb: "Custom Widgets", name: "Custom Widgets"} data: {breadcrumb: "Custom Widgets", name: "Custom Widgets"}
}, },
{
path: "etherpad",
component: EtherpadWidgetConfigComponent,
data: {breadcrumb: "Etherpad Widgets", name: "Etherpad Widgets"}
},
], ],
}, },
], ],

View File

@ -0,0 +1,18 @@
<my-widget-config [widgetComponent]="this">
<ng-template #widgetParamsTemplate let-widget="widget">
<label class="label-block">
Pad 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">
Pad URL
<input type="text" class="form-control"
placeholder="https://demo.riot.im/etherpad/p/MyCoolPadName"
[(ngModel)]="widget.dimension.newUrl" name="widget-url-{{widget.id}}"
[disabled]="isUpdating"/>
</label>
</ng-template>
</my-widget-config>

View File

@ -0,0 +1,34 @@
import { NewWidgetComponent } from "../widget.component";
import { EditableWidget, WIDGET_ETHERPAD } from "../../../shared/models/widget";
import { Component } from "@angular/core";
import { EtherpadWidget } from "../../../shared/models/integration";
import { SessionStorage } from "../../../shared/SessionStorage";
import { NameService } from "../../../shared/services/name.service";
@Component({
templateUrl: "etherpad.widget.component.html",
styleUrls: ["etherpad.widget.component.scss"],
})
export class EtherpadWidgetConfigComponent extends NewWidgetComponent {
private etherpadWidget: EtherpadWidget = <EtherpadWidget>SessionStorage.editIntegration;
constructor(private nameService: NameService) {
super(WIDGET_ETHERPAD, "Etherpad Widget", "generic", "etherpad");
}
protected OnNewWidgetPrepared(widget: EditableWidget): void {
const name = this.nameService.getHumanReadableName();
let template = "https://demo.riot.im/etherpad/p/$roomId_$padName";
if (this.etherpadWidget.options && this.etherpadWidget.options.defaultUrl) {
template = this.etherpadWidget.options.defaultUrl;
}
template = template.replace("$roomId", encodeURIComponent(SessionStorage.roomId));
template = template.replace("$padName", encodeURIComponent(name));
widget.dimension.newUrl = template;
widget.dimension.newName = name;
}
}

View File

@ -1,9 +1,9 @@
import { EventEmitter } from "@angular/core";
import { convertScalarWidgetsToDtos, EditableWidget } from "../../shared/models/widget"; import { convertScalarWidgetsToDtos, EditableWidget } from "../../shared/models/widget";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { ScalarClientApiService } from "../../shared/services/scalar-client-api.service"; import { ScalarClientApiService } from "../../shared/services/scalar-client-api.service";
import { ServiceLocator } from "../../shared/services/locator.service"; import { ServiceLocator } from "../../shared/services/locator.service";
import { SessionStorage } from "../../shared/SessionStorage"; import { SessionStorage } from "../../shared/SessionStorage";
import { OnInit } from "@angular/core";
const SCALAR_WIDGET_LINKS = [ const SCALAR_WIDGET_LINKS = [
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?url=", "https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?url=",
@ -12,7 +12,7 @@ const SCALAR_WIDGET_LINKS = [
"https://demo.riot.im/scalar/api/widgets/__TYPE__.html?url=", "https://demo.riot.im/scalar/api/widgets/__TYPE__.html?url=",
]; ];
export class NewWidgetComponent { export class NewWidgetComponent implements OnInit {
public isLoading = true; public isLoading = true;
public isUpdating = false; public isUpdating = false;
@ -27,40 +27,32 @@ export class NewWidgetComponent {
private window = ServiceLocator.injector.get(Window); private window = ServiceLocator.injector.get(Window);
private scalarApi = ServiceLocator.injector.get(ScalarClientApiService); private scalarApi = ServiceLocator.injector.get(ScalarClientApiService);
protected OnNewWidgetPrepared = new EventEmitter<EditableWidget>();
protected OnWidgetsDiscovered = new EventEmitter<EditableWidget[]>();
protected OnWidgetBeforeAdd = new EventEmitter<EditableWidget>();
protected OnWidgetAfterAdd = new EventEmitter<EditableWidget>();
protected OnWidgetPreparedForEdit = new EventEmitter<EditableWidget>();
protected OnWidgetBeforeEdit = new EventEmitter<EditableWidget>();
protected OnWidgetAfterEdit = new EventEmitter<EditableWidget>();
protected OnWidgetBeforeDelete = new EventEmitter<EditableWidget>();
protected OnWidgetAfterDelete = new EventEmitter<EditableWidget>();
constructor(private widgetTypes: string[], constructor(private widgetTypes: string[],
public defaultName: string, public defaultName: string,
private wrapperId = "generic", private wrapperId = "generic",
private scalarWrapperId = null) { private scalarWrapperId = null) {
this.isLoading = true; this.isLoading = true;
this.isUpdating = false; this.isUpdating = false;
}
if (wrapperId) { public ngOnInit(): void {
this.wrapperUrl = this.window.location.origin + "/widgets/" + wrapperId + "?url="; if (this.wrapperId) {
this.wrapperUrl = this.window.location.origin + "/widgets/" + this.wrapperId + "?url=";
if (!scalarWrapperId) scalarWrapperId = wrapperId; if (!this.scalarWrapperId) this.scalarWrapperId = this.wrapperId;
for (let widgetLink of SCALAR_WIDGET_LINKS) { for (let widgetLink of SCALAR_WIDGET_LINKS) {
this.scalarWrapperUrls.push(widgetLink.replace("__TYPE__", scalarWrapperId)); this.scalarWrapperUrls.push(widgetLink.replace("__TYPE__", this.scalarWrapperId));
} }
} }
this.prepareNewWidget(); this.prepareNewWidget();
this.getWidgetsOfType(widgetTypes).then(widgets => { this.getWidgetsOfType(this.widgetTypes).then(widgets => {
this.widgets = widgets; this.widgets = widgets;
for (let widget of this.widgets) { for (let widget of this.widgets) {
this.unpackWidget(widget); this.unpackWidget(widget);
} }
this.OnWidgetsDiscovered.emit(this.widgets); this.OnWidgetsDiscovered(this.widgets);
this.isLoading = false; this.isLoading = false;
this.isUpdating = false; this.isUpdating = false;
@ -155,7 +147,8 @@ export class NewWidgetComponent {
newData: {}, newData: {},
}, },
}; };
this.OnNewWidgetPrepared.emit(this.newWidget); console.log("Emitting new widget prepared event");
this.OnNewWidgetPrepared(this.newWidget);
} }
/** /**
@ -238,12 +231,12 @@ export class NewWidgetComponent {
this.packWidget(this.newWidget); this.packWidget(this.newWidget);
this.isUpdating = true; this.isUpdating = true;
this.OnWidgetBeforeAdd.emit(this.newWidget); this.OnWidgetBeforeAdd(this.newWidget);
return this.scalarApi.setWidget(SessionStorage.roomId, this.newWidget) return this.scalarApi.setWidget(SessionStorage.roomId, this.newWidget)
.then(() => this.widgets.push(this.newWidget)) .then(() => this.widgets.push(this.newWidget))
.then(() => { .then(() => {
this.isUpdating = false; this.isUpdating = false;
this.OnWidgetAfterAdd.emit(this.newWidget); this.OnWidgetAfterAdd(this.newWidget);
this.prepareNewWidget(); this.prepareNewWidget();
this.toaster.pop("success", "Widget added!"); this.toaster.pop("success", "Widget added!");
}) })
@ -268,11 +261,11 @@ export class NewWidgetComponent {
this.packWidget(widget); this.packWidget(widget);
this.isUpdating = true; this.isUpdating = true;
this.OnWidgetBeforeEdit.emit(widget); this.OnWidgetBeforeEdit(widget);
return this.scalarApi.setWidget(SessionStorage.roomId, widget) return this.scalarApi.setWidget(SessionStorage.roomId, widget)
.then(() => { .then(() => {
this.isUpdating = false; this.isUpdating = false;
this.OnWidgetAfterEdit.emit(widget); this.OnWidgetAfterEdit(widget);
this.toaster.pop("success", "Widget updated!"); this.toaster.pop("success", "Widget updated!");
}) })
.catch(err => { .catch(err => {
@ -289,12 +282,12 @@ export class NewWidgetComponent {
*/ */
public removeWidget(widget: EditableWidget): Promise<any> { public removeWidget(widget: EditableWidget): Promise<any> {
this.isUpdating = true; this.isUpdating = true;
this.OnWidgetBeforeDelete.emit(widget); this.OnWidgetBeforeDelete(widget);
return this.scalarApi.deleteWidget(SessionStorage.roomId, widget) return this.scalarApi.deleteWidget(SessionStorage.roomId, widget)
.then(() => this.widgets.splice(this.widgets.indexOf(widget), 1)) .then(() => this.widgets.splice(this.widgets.indexOf(widget), 1))
.then(() => { .then(() => {
this.isUpdating = false; this.isUpdating = false;
this.OnWidgetAfterDelete.emit(widget); this.OnWidgetAfterDelete(widget);
this.toaster.pop("success", "Widget deleted!"); this.toaster.pop("success", "Widget deleted!");
}) })
.catch(err => { .catch(err => {
@ -310,6 +303,90 @@ export class NewWidgetComponent {
*/ */
public resetWidget(widget: EditableWidget) { public resetWidget(widget: EditableWidget) {
this.unpackWidget(widget); this.unpackWidget(widget);
this.OnWidgetPreparedForEdit.emit(widget); this.OnWidgetPreparedForEdit(widget);
}
// Component hooks below here
// ------------------------------------------------------------------
/**
* Called when a new widget has been created in the newWidget field
* @param {EditableWidget} _widget The widget that has been prepared
*/
protected OnNewWidgetPrepared(_widget: EditableWidget): void {
// Component hook
}
/**
* Called after all the widgets have been discovered and unpacked for the room.
* @param {EditableWidget[]} _widgets The widgets that were discovered
*/
protected OnWidgetsDiscovered(_widgets: EditableWidget[]): void {
// Component hook
}
/**
* Called before the widget is added to the room, but after the Dimension-specific
* settings have been copied over to the primary fields.
* @param {EditableWidget} _widget The widget that is about to be added
*/
protected OnWidgetBeforeAdd(_widget: EditableWidget): void {
// Component hook
}
/**
* Called after the widget has been added to the room, but before the newWidget field
* has been set to a new widget.
* @param {EditableWidget} _widget The widget that has been added.
*/
protected OnWidgetAfterAdd(_widget: EditableWidget): void {
// Component hook
}
/**
* Called when the given widget has been asked to be prepared for editing. At this point
* the widget is not being persisted to the room, it is just updating the EditingWidget's
* properties for the user's ability to edit it.
* @param {EditableWidget} _widget The widget that has been prepared for editing
*/
protected OnWidgetPreparedForEdit(_widget: EditableWidget): void {
// Component hook
}
/**
* Called before the given widget has been updated in the room, but after the
* Dimension-specific settings have been copied over to the primary fields.
* This is not called for widgets being deleted.
* @param {EditableWidget} _widget The widget about to be edited
*/
protected OnWidgetBeforeEdit(_widget: EditableWidget): void {
// Component hook
}
/**
* Called after a given widget has been updated in the room. This is not called for
* widgets being deleted.
* @param {EditableWidget} _widget The widget that has been updated.
*/
protected OnWidgetAfterEdit(_widget: EditableWidget): void {
// Component hook
}
/**
* Called before the given widget has been removed from the room. No changes to the
* widget have been made at this point.
* @param {EditableWidget} _widget The widget about to be deleted.
*/
protected OnWidgetBeforeDelete(_widget: EditableWidget): void {
// Component hook
}
/**
* Called after a given widget has been deleted from the room. The widget will be in
* the deleted state and will no longer be tracked anywhere on the component.
* @param {EditableWidget} _widget The widget that has been deleted.
*/
protected OnWidgetAfterDelete(_widget: EditableWidget): void {
// Component hook
} }
} }

View File

@ -0,0 +1,18 @@
import { Injectable } from "@angular/core";
import * as gobyInit from "goby";
const goby = gobyInit.init({
// Converts words to a url-safe name
// Ie: "hello world how-are you" becomes "HelloWorldHowAreYou"
decorator: parts => parts.map(p => p ? p.split('-').map(p2 => p2 ? p2[0].toUpperCase() + p2.substring(1).toLowerCase() : '').join('') : '').join(''),
});
@Injectable()
export class NameService {
constructor() {
}
public getHumanReadableName(): string {
return goby.generate(["adj", "pre", "suf"]);
}
}