mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
parent
38ac6ec4e9
commit
c200020e55
7
config/integrations/youtube_widget.yaml
Normal file
7
config/integrations/youtube_widget.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# All this configuration does is make "Youtube Widget" available in the UI
|
||||||
|
type: "widget"
|
||||||
|
integrationType: "youtube"
|
||||||
|
enabled: true
|
||||||
|
name: "YouTube Video"
|
||||||
|
about: "Embed a YouTube, Vimeo, or DailyMotion video"
|
||||||
|
avatar: "img/avatars/youtube.png"
|
48
package-lock.json
generated
48
package-lock.json
generated
@ -2096,6 +2096,16 @@
|
|||||||
"minimalistic-crypto-utils": "1.0.1"
|
"minimalistic-crypto-utils": "1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"embed-video": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/embed-video/-/embed-video-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-1/JouzRkIg9pXbM6YCHhpgjI4fk=",
|
||||||
|
"requires": {
|
||||||
|
"fetch-ponyfill": "4.1.0",
|
||||||
|
"lodash.escape": "4.0.1",
|
||||||
|
"promise-polyfill": "6.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"emojis-list": {
|
"emojis-list": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
|
||||||
@ -2107,6 +2117,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz",
|
||||||
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
|
"integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA="
|
||||||
},
|
},
|
||||||
|
"encoding": {
|
||||||
|
"version": "0.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
|
||||||
|
"integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
|
||||||
|
"requires": {
|
||||||
|
"iconv-lite": "0.4.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"enhanced-resolve": {
|
"enhanced-resolve": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.3.0.tgz",
|
||||||
@ -2621,6 +2639,14 @@
|
|||||||
"websocket-driver": "0.6.5"
|
"websocket-driver": "0.6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fetch-ponyfill": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz",
|
||||||
|
"integrity": "sha1-rjzl9zLGReq4fkroeTQUcJsjmJM=",
|
||||||
|
"requires": {
|
||||||
|
"node-fetch": "1.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"file-loader": {
|
"file-loader": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.0.0.tgz",
|
||||||
@ -3739,8 +3765,7 @@
|
|||||||
"is-stream": {
|
"is-stream": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-svg": {
|
"is-svg": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@ -4028,6 +4053,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||||
},
|
},
|
||||||
|
"lodash.escape": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz",
|
||||||
|
"integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg="
|
||||||
|
},
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
@ -4470,6 +4500,15 @@
|
|||||||
"minimatch": "3.0.4"
|
"minimatch": "3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "1.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
|
||||||
|
"integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
|
||||||
|
"requires": {
|
||||||
|
"encoding": "0.1.12",
|
||||||
|
"is-stream": "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.6.33",
|
"version": "0.6.33",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.6.33.tgz",
|
||||||
@ -6555,6 +6594,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||||
},
|
},
|
||||||
|
"promise-polyfill": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.0.2.tgz",
|
||||||
|
"integrity": "sha1-2chtPcTcLfkBboiUbe/Wm0m0EWI="
|
||||||
|
},
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz",
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"db-migrate": "^0.10.0-beta.23",
|
"db-migrate": "^0.10.0-beta.23",
|
||||||
"db-migrate-sqlite3": "^0.2.1",
|
"db-migrate-sqlite3": "^0.2.1",
|
||||||
"dns-then": "^0.1.0",
|
"dns-then": "^0.1.0",
|
||||||
|
"embed-video": "^2.0.0",
|
||||||
"express": "^4.15.4",
|
"express": "^4.15.4",
|
||||||
"js-yaml": "^3.9.1",
|
"js-yaml": "^3.9.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
|
@ -27,6 +27,8 @@ import { MyFilterPipe } from "./shared/my-filter.pipe";
|
|||||||
import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component";
|
import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component";
|
||||||
import { ToggleFullscreenDirective } from "./shared/toggle-fullscreen.directive";
|
import { ToggleFullscreenDirective } from "./shared/toggle-fullscreen.directive";
|
||||||
import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component";
|
import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component";
|
||||||
|
import { YoutubeWidgetConfigComponent } from "./configs/widget/youtube/youtube-config.component";
|
||||||
|
import { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -55,6 +57,8 @@ import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button
|
|||||||
GenericWidgetWrapperComponent,
|
GenericWidgetWrapperComponent,
|
||||||
ToggleFullscreenDirective,
|
ToggleFullscreenDirective,
|
||||||
FullscreenButtonComponent,
|
FullscreenButtonComponent,
|
||||||
|
YoutubeWidgetConfigComponent,
|
||||||
|
VideoWidgetWrapperComponent,
|
||||||
|
|
||||||
// Vendor
|
// Vendor
|
||||||
],
|
],
|
||||||
@ -73,6 +77,7 @@ import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button
|
|||||||
TravisCiConfigComponent,
|
TravisCiConfigComponent,
|
||||||
IrcConfigComponent,
|
IrcConfigComponent,
|
||||||
CustomWidgetConfigComponent,
|
CustomWidgetConfigComponent,
|
||||||
|
YoutubeWidgetConfigComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
@ -2,11 +2,13 @@ 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 { VideoWidgetWrapperComponent } from "./widget_wrappers/video/video.component";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: "", component: HomeComponent},
|
{path: "", component: HomeComponent},
|
||||||
{path: "riot", component: RiotComponent},
|
{path: "riot", component: RiotComponent},
|
||||||
{path: "widgets/generic", component: GenericWidgetWrapperComponent},
|
{path: "widgets/generic", component: GenericWidgetWrapperComponent},
|
||||||
|
{path: "widgets/video", component: VideoWidgetWrapperComponent},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const routing = RouterModule.forRoot(routes);
|
export const routing = RouterModule.forRoot(routes);
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="text" class="form-control"
|
<input type="text" class="form-control"
|
||||||
placeholder="Custom widget URL"
|
placeholder="Custom widget URL"
|
||||||
[(ngModel)]="widgetUrl" name="widgetUrl"
|
[(ngModel)]="newWidgetUrl" name="newWidgetUrl"
|
||||||
[disabled]="isUpdating">
|
[disabled]="isUpdating">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button type="submit" class="btn btn-success" [disabled]="isUpdating">
|
<button type="submit" class="btn btn-success" [disabled]="isUpdating">
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { Component } from "@angular/core";
|
import { Component } from "@angular/core";
|
||||||
import { ModalComponent, DialogRef } from "ngx-modialog";
|
import { ModalComponent, DialogRef } from "ngx-modialog";
|
||||||
import { WidgetComponent, SCALAR_WIDGET_LINKS } from "../widget.component";
|
import { WidgetComponent } from "../widget.component";
|
||||||
import { ScalarService } from "../../../shared/scalar.service";
|
import { ScalarService } from "../../../shared/scalar.service";
|
||||||
import { ConfigModalContext } from "../../../integration/integration.component";
|
import { ConfigModalContext } from "../../../integration/integration.component";
|
||||||
import { ToasterService } from "angular2-toaster";
|
import { ToasterService } from "angular2-toaster";
|
||||||
import { Widget, WIDGET_DIM_CUSTOM, WIDGET_SCALAR_CUSTOM } from "../../../shared/models/widget";
|
import { WIDGET_DIM_CUSTOM, WIDGET_SCALAR_CUSTOM } from "../../../shared/models/widget";
|
||||||
|
|
||||||
// TODO: A lot of this can probably be abstracted out for other widgets (even the UI), possibly even for other integrations
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "my-customwidget-config",
|
selector: "my-customwidget-config",
|
||||||
@ -15,136 +13,20 @@ import { Widget, WIDGET_DIM_CUSTOM, WIDGET_SCALAR_CUSTOM } from "../../../shared
|
|||||||
})
|
})
|
||||||
export class CustomWidgetConfigComponent extends WidgetComponent implements ModalComponent<ConfigModalContext> {
|
export class CustomWidgetConfigComponent extends WidgetComponent implements ModalComponent<ConfigModalContext> {
|
||||||
|
|
||||||
public isLoading = true;
|
|
||||||
public isUpdating = false;
|
|
||||||
public widgets: Widget[];
|
|
||||||
public widgetUrl = "";
|
|
||||||
|
|
||||||
private toggledWidgets: string[] = [];
|
|
||||||
private wrapperUrl = "";
|
|
||||||
private requestedEditId: string = null;
|
|
||||||
|
|
||||||
constructor(public dialog: DialogRef<ConfigModalContext>,
|
constructor(public dialog: DialogRef<ConfigModalContext>,
|
||||||
private toaster: ToasterService,
|
toaster: ToasterService,
|
||||||
scalarService: ScalarService,
|
scalarService: ScalarService,
|
||||||
window: Window) {
|
window: Window) {
|
||||||
super(scalarService, dialog.context.roomId);
|
super(
|
||||||
|
toaster,
|
||||||
this.requestedEditId = dialog.context.integrationId;
|
scalarService,
|
||||||
|
dialog.context.roomId,
|
||||||
this.getWidgetsOfType(WIDGET_DIM_CUSTOM, WIDGET_SCALAR_CUSTOM).then(widgets => {
|
window,
|
||||||
this.widgets = widgets;
|
WIDGET_DIM_CUSTOM,
|
||||||
this.isLoading = false;
|
WIDGET_SCALAR_CUSTOM,
|
||||||
this.isUpdating = false;
|
dialog.context.integrationId,
|
||||||
|
"Custom Widget",
|
||||||
// Unwrap URLs for easy-editing
|
"generic" // wrapper
|
||||||
for (let widget of this.widgets) {
|
);
|
||||||
widget.url = this.getWrappedUrl(widget.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// See if we should request editing a particular widget
|
|
||||||
if (this.requestedEditId) {
|
|
||||||
for (let widget of this.widgets) {
|
|
||||||
if (widget.id === this.requestedEditId) {
|
|
||||||
console.log("Requesting edit for " + widget.id);
|
|
||||||
this.editWidget(widget);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.wrapperUrl = window.location.origin + "/widgets/generic?url=";
|
|
||||||
}
|
|
||||||
|
|
||||||
private getWrappedUrl(url: string): string {
|
|
||||||
const urls = [this.wrapperUrl].concat(SCALAR_WIDGET_LINKS);
|
|
||||||
for (let scalarUrl of urls) {
|
|
||||||
if (url.startsWith(scalarUrl)) {
|
|
||||||
return decodeURIComponent(url.substring(scalarUrl.length));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private wrapUrl(url: string): string {
|
|
||||||
return this.wrapperUrl + encodeURIComponent(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public addWidget() {
|
|
||||||
let constructedWidget: Widget = {
|
|
||||||
id: "dimension-" + (new Date().getTime()),
|
|
||||||
url: this.wrapUrl(this.widgetUrl),
|
|
||||||
type: WIDGET_DIM_CUSTOM,
|
|
||||||
name: "Custom Widget",
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isUpdating = true;
|
|
||||||
this.scalarApi.setWidget(this.roomId, constructedWidget)
|
|
||||||
.then(() => this.widgets.push(constructedWidget))
|
|
||||||
.then(() => constructedWidget.url = this.getWrappedUrl(constructedWidget.url)) // unwrap for immediate editing
|
|
||||||
.then(() => {
|
|
||||||
this.isUpdating = false;
|
|
||||||
this.widgetUrl = "";
|
|
||||||
this.toaster.pop("success", "Widget added!");
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.toaster.pop("error", err.json().error);
|
|
||||||
console.error(err);
|
|
||||||
this.isUpdating = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public saveWidget(widget: Widget) {
|
|
||||||
if (widget.newUrl.trim().length === 0) {
|
|
||||||
this.toaster.pop("warning", "Please enter a URL for the widget");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
widget.name = widget.newName || "Custom Widget";
|
|
||||||
widget.url = this.wrapUrl(widget.newUrl);
|
|
||||||
|
|
||||||
this.isUpdating = true;
|
|
||||||
this.scalarApi.setWidget(this.roomId, widget)
|
|
||||||
.then(() => this.toggleWidget(widget))
|
|
||||||
.then(() => {
|
|
||||||
this.isUpdating = false;
|
|
||||||
this.toaster.pop("success", "Widget updated!");
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.toaster.pop("error", err.json().error);
|
|
||||||
console.error(err);
|
|
||||||
this.isUpdating = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeWidget(widget: Widget) {
|
|
||||||
this.isUpdating = true;
|
|
||||||
this.scalarApi.deleteWidget(this.roomId, widget)
|
|
||||||
.then(() => this.widgets.splice(this.widgets.indexOf(widget), 1))
|
|
||||||
.then(() => {
|
|
||||||
this.isUpdating = false;
|
|
||||||
this.toaster.pop("success", "Widget deleted!");
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.toaster.pop("error", err.json().error);
|
|
||||||
console.error(err);
|
|
||||||
this.isUpdating = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public editWidget(widget: Widget) {
|
|
||||||
widget.newName = widget.name || "Custom Widget";
|
|
||||||
widget.newUrl = widget.url;
|
|
||||||
this.toggleWidget(widget);
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggleWidget(widget: Widget) {
|
|
||||||
let idx = this.toggledWidgets.indexOf(widget.id);
|
|
||||||
if (idx === -1) this.toggledWidgets.push(widget.id);
|
|
||||||
else this.toggledWidgets.splice(idx, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public isWidgetToggled(widget: Widget) {
|
|
||||||
return this.toggledWidgets.indexOf(widget.id) !== -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,68 @@
|
|||||||
import { ScalarService } from "../../shared/scalar.service";
|
import { ScalarService } from "../../shared/scalar.service";
|
||||||
import { Widget, ScalarToWidgets } from "../../shared/models/widget";
|
import { Widget, ScalarToWidgets } from "../../shared/models/widget";
|
||||||
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
|
||||||
export const SCALAR_WIDGET_LINKS = [
|
const SCALAR_WIDGET_LINKS = [
|
||||||
"https://scalar-staging.riot.im/scalar/api/widgets/generic.html?url=",
|
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?url=",
|
||||||
"https://scalar-staging.vector.im/scalar/api/widgets/generic.html?url=",
|
"https://scalar-staging.vector.im/scalar/api/widgets/__TYPE__.html?url=",
|
||||||
"https://scalar-develop.riot.im/scalar/api/widgets/generic.html?url=",
|
"https://scalar-develop.riot.im/scalar/api/widgets/__TYPE__.html?url=",
|
||||||
"https://demo.riot.im/scalar/api/widgets/generic.html?url=",
|
"https://demo.riot.im/scalar/api/widgets/__TYPE__.html?url=",
|
||||||
];
|
];
|
||||||
|
|
||||||
export class WidgetComponent {
|
export class WidgetComponent {
|
||||||
|
|
||||||
constructor(protected scalarApi: ScalarService, protected roomId: string) {
|
public isLoading = true;
|
||||||
|
public isUpdating = false;
|
||||||
|
public widgets: Widget[];
|
||||||
|
public newWidgetUrl: string = "";
|
||||||
|
public newWidgetName: string = "";
|
||||||
|
|
||||||
|
private toggledWidgetIds: string[] = [];
|
||||||
|
private wrapperUrl = "";
|
||||||
|
private scalarWrapperUrls: string[] = [];
|
||||||
|
|
||||||
|
constructor(protected toaster: ToasterService,
|
||||||
|
protected scalarApi: ScalarService,
|
||||||
|
protected roomId: string,
|
||||||
|
window: Window,
|
||||||
|
private primaryWidgetType: string,
|
||||||
|
alternateWidgetType: string,
|
||||||
|
requestedEditId: string,
|
||||||
|
private defaultName: string,
|
||||||
|
wrapperId = "generic",
|
||||||
|
scalarWrapperId = null) {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.wrapperUrl = window.location.origin + "/widgets/" + wrapperId + "?url=";
|
||||||
|
|
||||||
|
if (!scalarWrapperId) scalarWrapperId = wrapperId;
|
||||||
|
for (let widgetLink of SCALAR_WIDGET_LINKS) {
|
||||||
|
this.scalarWrapperUrls.push(widgetLink.replace("__TYPE__", scalarWrapperId));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getWidgetsOfType(type: string, altType: string = null): Promise<Widget[]> {
|
this.getWidgetsOfType(primaryWidgetType, alternateWidgetType).then(widgets => {
|
||||||
|
this.widgets = widgets;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.isUpdating = false;
|
||||||
|
|
||||||
|
// Unwrap URLs for easy-editing
|
||||||
|
for (let widget of this.widgets) {
|
||||||
|
this.setWidgetUrl(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if we should request editing a particular widget
|
||||||
|
if (requestedEditId) {
|
||||||
|
for (let widget of this.widgets) {
|
||||||
|
if (widget.id === requestedEditId) {
|
||||||
|
console.log("Requesting edit for " + widget.id);
|
||||||
|
this.editWidget(widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWidgetsOfType(type: string, altType: string): Promise<Widget[]> {
|
||||||
return this.scalarApi.getWidgets(this.roomId)
|
return this.scalarApi.getWidgets(this.roomId)
|
||||||
.then(resp => ScalarToWidgets(resp))
|
.then(resp => ScalarToWidgets(resp))
|
||||||
.then(widgets => {
|
.then(widgets => {
|
||||||
@ -27,4 +76,109 @@ export class WidgetComponent {
|
|||||||
return filtered;
|
return filtered;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getWrappedUrl(url: string): string {
|
||||||
|
const urls = [this.wrapperUrl].concat(this.scalarWrapperUrls);
|
||||||
|
console.log(urls);
|
||||||
|
for (let scalarUrl of urls) {
|
||||||
|
if (url.startsWith(scalarUrl)) {
|
||||||
|
return decodeURIComponent(url.substring(scalarUrl.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrapUrl(url: string): string {
|
||||||
|
return this.wrapperUrl + encodeURIComponent(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setWidgetUrl(widget: Widget) {
|
||||||
|
console.log(widget);
|
||||||
|
widget.url = this.getWrappedUrl(widget.url);
|
||||||
|
|
||||||
|
// Use the Dimension-specific URL override if one is present
|
||||||
|
if (widget.data && widget.data.dimOriginalUrl) {
|
||||||
|
widget.url = widget.data.dimOriginalUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addWidget(data: any = null) {
|
||||||
|
let constructedWidget: Widget = {
|
||||||
|
id: "dimension-" + this.primaryWidgetType + "-" + (new Date().getTime()),
|
||||||
|
url: this.wrapUrl(this.newWidgetUrl),
|
||||||
|
type: this.primaryWidgetType,
|
||||||
|
name: this.newWidgetName || this.defaultName,
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isUpdating = true;
|
||||||
|
this.scalarApi.setWidget(this.roomId, constructedWidget)
|
||||||
|
.then(() => this.widgets.push(constructedWidget))
|
||||||
|
.then(() => this.setWidgetUrl(constructedWidget))
|
||||||
|
.then(() => {
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.newWidgetUrl = "";
|
||||||
|
this.newWidgetName = "";
|
||||||
|
this.toaster.pop("success", "Widget added!");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.toaster.pop("error", err.json().error);
|
||||||
|
console.error(err);
|
||||||
|
this.isUpdating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveWidget(widget: Widget) {
|
||||||
|
if (widget.newUrl.trim().length === 0) {
|
||||||
|
this.toaster.pop("warning", "Please enter a URL for the widget");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.name = widget.newName || this.defaultName;
|
||||||
|
widget.url = this.wrapUrl(widget.newUrl);
|
||||||
|
|
||||||
|
this.isUpdating = true;
|
||||||
|
this.scalarApi.setWidget(this.roomId, widget)
|
||||||
|
.then(() => this.toggleWidget(widget))
|
||||||
|
.then(() => {
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.toaster.pop("success", "Widget updated!");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.toaster.pop("error", err.json().error);
|
||||||
|
console.error(err);
|
||||||
|
this.isUpdating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeWidget(widget: Widget) {
|
||||||
|
this.isUpdating = true;
|
||||||
|
this.scalarApi.deleteWidget(this.roomId, widget)
|
||||||
|
.then(() => this.widgets.splice(this.widgets.indexOf(widget), 1))
|
||||||
|
.then(() => {
|
||||||
|
this.isUpdating = false;
|
||||||
|
this.toaster.pop("success", "Widget deleted!");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.toaster.pop("error", err.json().error);
|
||||||
|
console.error(err);
|
||||||
|
this.isUpdating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public editWidget(widget: Widget) {
|
||||||
|
widget.newName = widget.name || this.defaultName;
|
||||||
|
widget.newUrl = widget.url;
|
||||||
|
this.toggleWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleWidget(widget: Widget) {
|
||||||
|
let idx = this.toggledWidgetIds.indexOf(widget.id);
|
||||||
|
if (idx === -1) this.toggledWidgetIds.push(widget.id);
|
||||||
|
else this.toggledWidgetIds.splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isWidgetToggled(widget: Widget) {
|
||||||
|
return this.toggledWidgetIds.indexOf(widget.id) !== -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
65
web/app/configs/widget/youtube/youtube-config.component.html
Normal file
65
web/app/configs/widget/youtube/youtube-config.component.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<div class="config-wrapper">
|
||||||
|
<img src="/img/close.svg" (click)="dialog.close()" class="close-icon">
|
||||||
|
<div class="config-header">
|
||||||
|
<img src="/img/avatars/youtube.png">
|
||||||
|
<h4>Configure video widgets</h4>
|
||||||
|
</div>
|
||||||
|
<div class="config-content" *ngIf="isLoading">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<p><i class="fa fa-circle-notch fa-spin"></i> Loading widgets...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="config-content" *ngIf="!isLoading">
|
||||||
|
<form (submit)="validateAndAddWidget()" novalidate name="addForm">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8" style="margin-bottom: 12px;">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="YouTube, Vimeo, or DailyMotion video URL"
|
||||||
|
[(ngModel)]="newWidgetUrl" name="newWidgetUrl"
|
||||||
|
[disabled]="isUpdating">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="submit" class="btn btn-success" [disabled]="isUpdating">
|
||||||
|
<i class="fa fa-plus-circle"></i> Add Widget
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-12 removable widget-item" *ngFor="let widget of widgets trackById">
|
||||||
|
{{ widget.name || widget.url }} <span class="text-muted" *ngIf="widget.ownerId">(added by {{ widget.ownerId }})</span>
|
||||||
|
<button type="button" class="btn btn-outline-info btn-sm" (click)="editWidget(widget)"
|
||||||
|
style="margin-top: -5px;" [disabled]="isUpdating">
|
||||||
|
<i class="fa fa-pencil"></i> Edit Widget
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger" (click)="removeWidget(widget)"
|
||||||
|
style="margin-top: -5px;" [disabled]="isUpdating">
|
||||||
|
<i class="fa fa-times"></i> Remove Widget
|
||||||
|
</button>
|
||||||
|
<div *ngIf="isWidgetToggled(widget)">
|
||||||
|
<label>
|
||||||
|
Widget Name
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="YouTube Widget"
|
||||||
|
[(ngModel)]="widget.newName" name="widget-name-{{widget.id}}"
|
||||||
|
[disabled]="isUpdating">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Video URL
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="YouTube, Vimeo, or DailyMotion video URL"
|
||||||
|
[(ngModel)]="widget.newUrl" name="widget-url-{{widget.id}}"
|
||||||
|
[disabled]="isUpdating">
|
||||||
|
</label>
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" (click)="validateAndSaveWidget(widget)">Save
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline btn-sm" (click)="toggleWidget(widget)">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,4 @@
|
|||||||
|
// component styles are encapsulated and only applied to their components
|
||||||
|
.widget-item {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
72
web/app/configs/widget/youtube/youtube-config.component.ts
Normal file
72
web/app/configs/widget/youtube/youtube-config.component.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { ModalComponent, DialogRef } from "ngx-modialog";
|
||||||
|
import { WidgetComponent } from "../widget.component";
|
||||||
|
import { ScalarService } from "../../../shared/scalar.service";
|
||||||
|
import { ConfigModalContext } from "../../../integration/integration.component";
|
||||||
|
import { ToasterService } from "angular2-toaster";
|
||||||
|
import { Widget, WIDGET_SCALAR_YOUTUBE, WIDGET_DIM_YOUTUBE } from "../../../shared/models/widget";
|
||||||
|
import * as embed from "embed-video";
|
||||||
|
import * as $ from "jquery";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "my-youtubewidget-config",
|
||||||
|
templateUrl: "youtube-config.component.html",
|
||||||
|
styleUrls: ["youtube-config.component.scss", "./../../config.component.scss"],
|
||||||
|
})
|
||||||
|
export class YoutubeWidgetConfigComponent extends WidgetComponent implements ModalComponent<ConfigModalContext> {
|
||||||
|
|
||||||
|
constructor(public dialog: DialogRef<ConfigModalContext>,
|
||||||
|
toaster: ToasterService,
|
||||||
|
scalarService: ScalarService,
|
||||||
|
window: Window) {
|
||||||
|
super(
|
||||||
|
toaster,
|
||||||
|
scalarService,
|
||||||
|
dialog.context.roomId,
|
||||||
|
window,
|
||||||
|
WIDGET_DIM_YOUTUBE,
|
||||||
|
WIDGET_SCALAR_YOUTUBE,
|
||||||
|
dialog.context.integrationId,
|
||||||
|
"Youtube Widget",
|
||||||
|
"video", // wrapper
|
||||||
|
"youtube" // scalar wrapper
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public validateAndAddWidget() {
|
||||||
|
const url = this.getSafeUrl(this.newWidgetUrl);
|
||||||
|
if (!url) {
|
||||||
|
this.toaster.pop("warning", "Please enter a YouTube, Vimeo, or DailyMotion video URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalUrl = this.newWidgetUrl;
|
||||||
|
this.newWidgetUrl = url;
|
||||||
|
this.addWidget({dimOriginalUrl: originalUrl});
|
||||||
|
}
|
||||||
|
|
||||||
|
public validateAndSaveWidget(widget: Widget) {
|
||||||
|
const url = this.getSafeUrl(widget.newUrl);
|
||||||
|
if (!url) {
|
||||||
|
this.toaster.pop("warning", "Please enter a YouTube, Vimeo, or DailyMotion video URL");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.data = {dimOriginalUrl: widget.newUrl};
|
||||||
|
widget.newUrl = url;
|
||||||
|
this.saveWidget(widget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSafeUrl(url) {
|
||||||
|
const embedCode = embed(url);
|
||||||
|
if (!embedCode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HACK: Grab the video URL from the iframe
|
||||||
|
url = $(embedCode).attr("src");
|
||||||
|
if (url.startsWith("//")) url = "https:" + url;
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import { ToasterService } from "angular2-toaster";
|
|||||||
import { Integration } from "../shared/models/integration";
|
import { Integration } from "../shared/models/integration";
|
||||||
import { IntegrationService } from "../shared/integration.service";
|
import { IntegrationService } from "../shared/integration.service";
|
||||||
import * as _ from "lodash";
|
import * as _ from "lodash";
|
||||||
import { WIDGET_DIM_CUSTOM } from "../shared/models/widget";
|
import { WIDGET_DIM_CUSTOM, WIDGET_DIM_YOUTUBE } from "../shared/models/widget";
|
||||||
import { IntegrationComponent } from "../integration/integration.component";
|
import { IntegrationComponent } from "../integration/integration.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -73,6 +73,9 @@ export class RiotComponent {
|
|||||||
if (this.requestedScreen === "type_" + WIDGET_DIM_CUSTOM) {
|
if (this.requestedScreen === "type_" + WIDGET_DIM_CUSTOM) {
|
||||||
type = "widget";
|
type = "widget";
|
||||||
integrationType = "customwidget";
|
integrationType = "customwidget";
|
||||||
|
} else if (this.requestedScreen === "type_" + WIDGET_DIM_YOUTUBE) {
|
||||||
|
type = "widget";
|
||||||
|
integrationType = "youtube";
|
||||||
} else {
|
} else {
|
||||||
console.log("Unknown screen requested: " + this.requestedScreen);
|
console.log("Unknown screen requested: " + this.requestedScreen);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { ContainerContent } from "ngx-modialog";
|
|||||||
import { IrcConfigComponent } from "../configs/irc/irc-config.component";
|
import { IrcConfigComponent } from "../configs/irc/irc-config.component";
|
||||||
import { TravisCiConfigComponent } from "../configs/travisci/travisci-config.component";
|
import { TravisCiConfigComponent } from "../configs/travisci/travisci-config.component";
|
||||||
import { CustomWidgetConfigComponent } from "../configs/widget/custom_widget/custom_widget-config.component";
|
import { CustomWidgetConfigComponent } from "../configs/widget/custom_widget/custom_widget-config.component";
|
||||||
|
import { YoutubeWidgetConfigComponent } from "../configs/widget/youtube/youtube-config.component";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class IntegrationService {
|
export class IntegrationService {
|
||||||
@ -19,7 +20,8 @@ export class IntegrationService {
|
|||||||
"irc": true,
|
"irc": true,
|
||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"customwidget": true
|
"customwidget": true,
|
||||||
|
"youtube": true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ export class IntegrationService {
|
|||||||
},
|
},
|
||||||
"widget": {
|
"widget": {
|
||||||
"customwidget": CustomWidgetConfigComponent,
|
"customwidget": CustomWidgetConfigComponent,
|
||||||
|
"youtube": YoutubeWidgetConfigComponent,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export const WIDGET_SCALAR_GRAFANA = "grafana";
|
|||||||
|
|
||||||
// Dimension has its own set of types to ensure that we don't conflict with Scalar
|
// Dimension has its own set of types to ensure that we don't conflict with Scalar
|
||||||
export const WIDGET_DIM_CUSTOM = "dimension-customwidget";
|
export const WIDGET_DIM_CUSTOM = "dimension-customwidget";
|
||||||
|
export const WIDGET_DIM_YOUTUBE = "dimension-youtube";
|
||||||
|
|
||||||
export interface Widget {
|
export interface Widget {
|
||||||
id: string;
|
id: string;
|
||||||
|
1
web/app/widget_wrappers/video/video.component.html
Normal file
1
web/app/widget_wrappers/video/video.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<iframe [src]="embedUrl" frameborder="0" allowfullscreen></iframe>
|
10
web/app/widget_wrappers/video/video.component.scss
Normal file
10
web/app/widget_wrappers/video/video.component.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// component styles are encapsulated and only applied to their components
|
||||||
|
iframe {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
19
web/app/widget_wrappers/video/video.component.ts
Normal file
19
web/app/widget_wrappers/video/video.component.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { ActivatedRoute } from "@angular/router";
|
||||||
|
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "my-video-widget-wrapper",
|
||||||
|
templateUrl: "video.component.html",
|
||||||
|
styleUrls: ["video.component.scss"],
|
||||||
|
})
|
||||||
|
export class VideoWidgetWrapperComponent {
|
||||||
|
|
||||||
|
public embedUrl: SafeUrl = null;
|
||||||
|
|
||||||
|
constructor(activatedRoute: ActivatedRoute, sanitizer: DomSanitizer) {
|
||||||
|
let params: any = activatedRoute.snapshot.queryParams;
|
||||||
|
this.embedUrl = sanitizer.bypassSecurityTrustResourceUrl(params.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
BIN
web/public/img/avatars/youtube.png
Normal file
BIN
web/public/img/avatars/youtube.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.0 KiB |
Loading…
Reference in New Issue
Block a user