diff --git a/web/app/app.module.ts b/web/app/app.module.ts index 7c52e09..c23d34e 100644 --- a/web/app/app.module.ts +++ b/web/app/app.module.ts @@ -39,6 +39,7 @@ import { GoogleCalendarWidgetConfigComponent } from "./configs/widget/google_cal import { GoogleDocsWidgetConfigComponent } from "./configs/widget/google_docs/gdoc.widget.component"; import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi.widget.component"; import { TwitchWidgetConfigComponent } from "./configs/widget/twitch/twitch.widget.component"; +import { YoutubeWidgetConfigComponent } from "./configs/widget/youtube/youtube.widget.component"; @NgModule({ imports: [ @@ -77,6 +78,7 @@ import { TwitchWidgetConfigComponent } from "./configs/widget/twitch/twitch.widg GoogleDocsWidgetConfigComponent, JitsiWidgetConfigComponent, TwitchWidgetConfigComponent, + YoutubeWidgetConfigComponent, // Vendor ], diff --git a/web/app/app.routing.ts b/web/app/app.routing.ts index 0f884a5..cbf414c 100644 --- a/web/app/app.routing.ts +++ b/web/app/app.routing.ts @@ -12,6 +12,7 @@ import { GoogleCalendarWidgetConfigComponent } from "./configs/widget/google_cal import { GoogleDocsWidgetConfigComponent } from "./configs/widget/google_docs/gdoc.widget.component"; import { JitsiWidgetConfigComponent } from "./configs/widget/jitsi/jitsi.widget.component"; import { TwitchWidgetConfigComponent } from "./configs/widget/twitch/twitch.widget.component"; +import { YoutubeWidgetConfigComponent } from "./configs/widget/youtube/youtube.widget.component"; const routes: Routes = [ {path: "", component: HomeComponent}, @@ -58,6 +59,11 @@ const routes: Routes = [ component: TwitchWidgetConfigComponent, data: {breadcrumb: "Twitch Livestream Widgets", name: "Twitch Livestream Widgets"} }, + { + path: "youtube", + component: YoutubeWidgetConfigComponent, + data: {breadcrumb: "Youtube Video Widgets", name: "Youtube Video Widgets"} + }, ], }, ], diff --git a/web/app/configs/widget/twitch/twitch.widget.component.ts b/web/app/configs/widget/twitch/twitch.widget.component.ts index 7514478..29908dd 100644 --- a/web/app/configs/widget/twitch/twitch.widget.component.ts +++ b/web/app/configs/widget/twitch/twitch.widget.component.ts @@ -34,7 +34,7 @@ export class TwitchWidgetConfigComponent extends WidgetComponent { private setTwitchUrl(widget: EditableWidget) { if (!widget.dimension.newData.channelName || widget.dimension.newData.channelName.trim().length === 0) { - throw new Error("Please enter a shared calendar ID"); + throw new Error("Please enter a channel name"); } widget.dimension.newUrl = "https://player.twitch.tv/?channel=$channelName"; diff --git a/web/app/configs/widget/youtube/youtube.widget.component.html b/web/app/configs/widget/youtube/youtube.widget.component.html new file mode 100644 index 0000000..5dc53b7 --- /dev/null +++ b/web/app/configs/widget/youtube/youtube.widget.component.html @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/web/app/configs/widget/youtube/youtube.widget.component.scss b/web/app/configs/widget/youtube/youtube.widget.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web/app/configs/widget/youtube/youtube.widget.component.ts b/web/app/configs/widget/youtube/youtube.widget.component.ts new file mode 100644 index 0000000..8cce0cb --- /dev/null +++ b/web/app/configs/widget/youtube/youtube.widget.component.ts @@ -0,0 +1,86 @@ +import { WidgetComponent } from "../widget.component"; +import { EditableWidget, WIDGET_YOUTUBE } from "../../../shared/models/widget"; +import { Component } from "@angular/core"; +import * as embed from "embed-video"; +import * as $ from "jquery"; +import * as url from "url"; + +@Component({ + templateUrl: "youtube.widget.component.html", + styleUrls: ["youtube.widget.component.scss"], +}) +export class YoutubeWidgetConfigComponent extends WidgetComponent { + constructor() { + super(WIDGET_YOUTUBE, "Video", "video", "youtube"); + } + + protected OnNewWidgetPrepared(widget: EditableWidget) { + widget.dimension.newData.videoUrl = ""; + } + + protected OnWidgetsDiscovered(widgets: EditableWidget[]) { + for (const widget of widgets) { + if (widget.data.videoUrl) continue; // Dimension widget + + if (widget.data.cUrl) { + // Scalar widget + widget.data.videoUrl = this.parseVideoUrl(widget.data.cUrl); + } else { + // Older Dimension widget - infer link + const parsedUrl = url.parse(widget.url, true); + if (parsedUrl.query["url"]) { + widget.data.videoUrl = this.parseVideoUrl(decodeURIComponent(parsedUrl.query["url"])); + } else console.warn("Cannot parse URL for " + widget.id); + } + } + } + + protected OnWidgetBeforeAdd(widget: EditableWidget) { + this.setVideoUrl(widget); + } + + protected OnWidgetBeforeEdit(widget: EditableWidget) { + this.setVideoUrl(widget); + } + + private setVideoUrl(widget: EditableWidget) { + if (!widget.dimension.newData.videoUrl || widget.dimension.newData.videoUrl.trim().length === 0) { + throw new Error("Please enter a video URL"); + } + + const videoUrl = this.getRealVideoUrl(widget.dimension.newData.videoUrl); + if (!videoUrl) { + throw new Error("Please enter a YouTube, Vimeo, or DailyMotion video URL"); + } + + widget.dimension.newUrl = videoUrl; + } + + private getRealVideoUrl(videoUrl): string { + const embedCode = embed(videoUrl); + if (!embedCode) { + return null; + } + + // HACK: Grab the video URL from the iframe embed code + videoUrl = $(embedCode).attr("src"); + if (videoUrl.startsWith("//")) videoUrl = "https:" + videoUrl; + + return videoUrl; + } + + private parseVideoUrl(videoUrl: string): string { + const parsed = url.parse(videoUrl); + + const realPath = parsed.path.split("?")[0]; + if (parsed.host.indexOf("youtube.com") !== -1) { + return "https://youtube.com/watch?v=" + realPath.substring("/embed/".length); + } else if (parsed.host.indexOf("vimeo.com") !== -1) { + return "https://vimeo.com/" + realPath.substring("/video/".length); + } else if (parsed.host.indexOf("dailymotion.com") !== -1) { + return "https://dailymotion.com/" + realPath.substring("/embed/".length); + } + + return videoUrl; + } +} \ No newline at end of file