aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/client/DocServer.ts6
-rw-r--r--src/client/apis/youtube/YoutubeBox.tsx72
-rw-r--r--src/client/apis/youtube/youtubeApiSample.js22
-rw-r--r--src/client/documents/Documents.ts20
-rw-r--r--src/client/views/MainView.tsx6
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/new_fields/URLField.ts3
-rw-r--r--src/server/Message.ts1
-rw-r--r--src/server/index.ts20
10 files changed, 135 insertions, 20 deletions
diff --git a/package.json b/package.json
index 2371d530e..2b1c8f262 100644
--- a/package.json
+++ b/package.json
@@ -102,6 +102,7 @@
"bluebird": "^3.5.3",
"body-parser": "^1.18.3",
"bootstrap": "^4.3.1",
+ "child_process": "^1.0.2",
"class-transformer": "^0.2.0",
"connect-flash": "^0.1.1",
"connect-mongo": "^2.0.3",
@@ -171,6 +172,7 @@
"react-simple-dropdown": "^3.2.3",
"react-split-pane": "^0.1.85",
"react-table": "^6.9.2",
+ "readline": "^1.3.0",
"request": "^2.88.0",
"request-image-size": "^2.1.0",
"request-promise": "^4.2.4",
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index cbcf751ee..c9cbce78e 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -47,6 +47,12 @@ export namespace DocServer {
}
}
+ export async function getYoutubeApiKey() {
+ let apiKey = await Utils.EmitCallback(_socket, MessageStore.YoutubeApiKey, undefined);
+ return apiKey;
+ }
+
+
export async function GetRefFields(ids: string[]): Promise<{ [id: string]: Opt<RefField> }> {
const requestedIds: string[] = [];
const waitingIds: string[] = [];
diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
new file mode 100644
index 000000000..ee190750f
--- /dev/null
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -0,0 +1,72 @@
+import "../../views/nodes/WebBox.scss";
+import React = require("react");
+import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
+import { HtmlField } from "../../../new_fields/HtmlField";
+import { WebField } from "../../../new_fields/URLField";
+import { observer } from "mobx-react";
+import { computed, reaction, IReactionDisposer } from 'mobx';
+import { DocumentDecorations } from "../../views/DocumentDecorations";
+import { InkingControl } from "../../views/InkingControl";
+import * as YoutubeApi from "./youtubeApiSample";
+import { Utils } from "../../../Utils";
+import { DocServer } from "../../DocServer";
+
+
+@observer
+export class YoutubeBox extends React.Component<FieldViewProps> {
+
+ private youtubeApiKey: string = "";
+
+ public static LayoutString() { return FieldView.LayoutString(YoutubeBox); }
+
+ async componentWillMount() {
+ let apiKey = await DocServer.getYoutubeApiKey();
+ this.youtubeApiKey = apiKey;
+ YoutubeApi.authorizedGetChannel(this.youtubeApiKey);
+ }
+
+ _ignore = 0;
+ onPreWheel = (e: React.WheelEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPrePointer = (e: React.PointerEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPostPointer = (e: React.PointerEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
+ }
+ onPostWheel = (e: React.WheelEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
+ }
+ render() {
+ let field = this.props.Document[this.props.fieldKey];
+ let view;
+ YoutubeApi.readFsFile();
+ if (field instanceof HtmlField) {
+ view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ } else if (field instanceof WebField) {
+ view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ } else {
+ view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ }
+ let content =
+ <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ {view}
+ </div>;
+
+ let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
+ let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
+ return (
+ <>
+ <div className={classname} >
+ {content}
+ </div>
+ {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
+ </>);
+ }
+} \ No newline at end of file
diff --git a/src/client/apis/youtube/youtubeApiSample.js b/src/client/apis/youtube/youtubeApiSample.js
index 07c3add36..7f14f2d3e 100644
--- a/src/client/apis/youtube/youtubeApiSample.js
+++ b/src/client/apis/youtube/youtubeApiSample.js
@@ -1,7 +1,5 @@
-let fs = require('fs');
-let readline = require('readline');
-let { google } = require('googleapis');
-let OAuth2 = google.auth.OAuth2;
+import { Utils } from "tslint";
+
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/youtube-nodejs-quickstart.json
@@ -10,18 +8,14 @@ let TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
let TOKEN_PATH = TOKEN_DIR + 'youtube-nodejs-quickstart.json';
-function readFsFile() {
- // Load client secrets from a local file.
- fs.readFile('client_secret.json', function processClientSecrets(err, content) {
- if (err) {
- console.log('Error loading client secret file: ' + err);
- return;
- }
- // Authorize a client with the loaded credentials, then call the YouTube API.
- authorize(JSON.parse(content), getChannel);
- });
+
+
+function authorizedGetChannel(apiKey) {
+ // Authorize a client with the loaded credentials, then call the YouTube API.
+ authorize(JSON.parse(apiKey), getChannel);
}
+
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index ab61b915c..fd4532807 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -23,7 +23,7 @@ import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
import { IconBox } from "../views/nodes/IconBox";
import { Field, Doc, Opt } from "../../new_fields/Doc";
import { OmitKeys } from "../../Utils";
-import { ImageField, VideoField, AudioField, PdfField, WebField } from "../../new_fields/URLField";
+import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
import { HtmlField } from "../../new_fields/HtmlField";
import { List } from "../../new_fields/List";
import { Cast, NumCast } from "../../new_fields/Types";
@@ -35,6 +35,8 @@ import { dropActionType } from "../util/DragManager";
import { DateField } from "../../new_fields/DateField";
import { UndoManager } from "../util/UndoManager";
import { RouteStore } from "../../server/RouteStore";
+import { createInstance } from "@react-pdf/renderer";
+import { YoutubeBox } from "../apis/youtube/YoutubeBox";
var requestImageSize = require('request-image-size');
var path = require('path');
@@ -113,6 +115,7 @@ export namespace Docs {
let audioProto: Doc;
let pdfProto: Doc;
let iconProto: Doc;
+ let youtubeProto: Doc;
const textProtoId = "textProto";
const histoProtoId = "histoProto";
const pdfProtoId = "pdfProto";
@@ -123,9 +126,10 @@ export namespace Docs {
const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
const iconProtoId = "iconProto";
+ const youtubeProtoId = "youtubeProto";
export function initProtos(): Promise<void> {
- return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId]).then(fields => {
+ return DocServer.GetRefFields([textProtoId, histoProtoId, collProtoId, imageProtoId, webProtoId, kvpProtoId, videoProtoId, audioProtoId, pdfProtoId, iconProtoId, youtubeProtoId]).then(fields => {
textProto = fields[textProtoId] as Doc || CreateTextPrototype();
histoProto = fields[histoProtoId] as Doc || CreateHistogramPrototype();
collProto = fields[collProtoId] as Doc || CreateCollectionPrototype();
@@ -136,6 +140,7 @@ export namespace Docs {
audioProto = fields[audioProtoId] as Doc || CreateAudioPrototype();
pdfProto = fields[pdfProtoId] as Doc || CreatePdfPrototype();
iconProto = fields[iconProtoId] as Doc || CreateIconPrototype();
+ youtubeProto = fields[youtubeProtoId] as Doc || CreateYoutubePrototype();
});
}
@@ -183,6 +188,13 @@ export namespace Docs {
{ x: 0, y: 0, width: 300, height: 300 });
return webProto;
}
+ function CreateYoutubePrototype(): Doc {
+ let webProto = setupPrototypeOptions(youtubeProtoId, "YOUTUBE_PROTO", YoutubeBox.LayoutString(),
+ { x: 0, y: 0, width: 300, height: 300 });
+ return webProto;
+ }
+
+
function CreateCollectionPrototype(): Doc {
let collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("data"),
{ panX: 0, panY: 0, scale: 1, width: 500, height: 500 });
@@ -238,6 +250,10 @@ export namespace Docs {
// doc.SetText(KeyStore.OverlayLayout, FixedCaption());
// return doc;
}
+ export function YoutubeDocument(url: string, options: DocumentOptions = {}) {
+ return CreateInstance(youtubeProto, new YoutubeField(new URL(url)), options);
+ }
+
export function VideoDocument(url: string, options: DocumentOptions = {}) {
return CreateInstance(videoProto, new VideoField(new URL(url)), options);
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 7d2aa3199..4868fa41c 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,5 +1,5 @@
import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell } from '@fortawesome/free-solid-svg-icons';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt, faBell, faPlay } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
@@ -89,6 +89,7 @@ export class MainView extends React.Component {
library.add(faFilm);
library.add(faMusic);
library.add(faTree);
+ library.add(faPlay);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -226,6 +227,7 @@ export class MainView extends React.Component {
let weburl = "https://cs.brown.edu/courses/cs166/";
let audiourl = "http://techslides.com/demos/samples/sample.mp3";
let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
+ let youtubeurl = "https://www.youtube.com/embed/TqcApsGRzWw";
let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
@@ -238,6 +240,7 @@ export class MainView extends React.Component {
let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+ let addYoutubeSearcher = action(() => Docs.YoutubeDocument(youtubeurl, { width: 200, height: 200, title: "youtube node" }));
let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
[React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
@@ -249,6 +252,7 @@ export class MainView extends React.Component {
[React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
[React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
[React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
+ [React.createRef<HTMLDivElement>(), "play", "Add Youtube Searcher", addYoutubeSearcher]
];
return < div id="add-nodes-menu" >
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 02396c3af..d242d8fad 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -17,6 +17,7 @@ import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
import { FieldView } from "./FieldView";
import { WebBox } from "./WebBox";
+import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import React = require("react");
import { FieldViewProps } from "./FieldView";
@@ -103,7 +104,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
render() {
if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
return <ObserverJsxParser
- components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox, YoutubeBox }}
bindings={this.CreateBindings()}
jsx={this.finalLayout}
showWarnings={true}
diff --git a/src/new_fields/URLField.ts b/src/new_fields/URLField.ts
index 4a2841fb6..6e4cfa2ed 100644
--- a/src/new_fields/URLField.ts
+++ b/src/new_fields/URLField.ts
@@ -41,4 +41,5 @@ export abstract class URLField extends ObjectField {
@Deserializable("image") export class ImageField extends URLField { }
@Deserializable("video") export class VideoField extends URLField { }
@Deserializable("pdf") export class PdfField extends URLField { }
-@Deserializable("web") export class WebField extends URLField { } \ No newline at end of file
+@Deserializable("web") export class WebField extends URLField { }
+@Deserializable("youtube") export class YoutubeField extends URLField { } \ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index e9a8b0f0c..ee9142222 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -45,4 +45,5 @@ export namespace MessageStore {
export const GetRefFields = new Message<string[]>("Get Ref Fields");
export const UpdateField = new Message<Diff>("Update Ref Field");
export const CreateField = new Message<Reference>("Create Ref Field");
+ export const YoutubeApiKey = new Message<string>("Youtube Api Key");
}
diff --git a/src/server/index.ts b/src/server/index.ts
index fd66c90b4..2629519a7 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -39,6 +39,11 @@ import { debug } from 'util';
import _ = require('lodash');
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
+//let fs = require('fs');
+let readline = require('readline');
+let { google } = require('googleapis');
+let OAuth2 = google.auth.OAuth2;
+
const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
@@ -310,6 +315,7 @@ server.on("connection", function (socket: Socket) {
Utils.AddServerHandler(socket, MessageStore.DeleteAll, deleteFields);
Utils.AddServerHandler(socket, MessageStore.CreateField, CreateField);
+ Utils.AddServerHandlerCallback(socket, MessageStore.YoutubeApiKey, GetYoutubeApiKey);
Utils.AddServerHandler(socket, MessageStore.UpdateField, diff => UpdateField(socket, diff));
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefField, GetRefField);
Utils.AddServerHandlerCallback(socket, MessageStore.GetRefFields, GetRefFields);
@@ -360,6 +366,17 @@ function GetRefFields([ids, callback]: [string[], (result?: Transferable[]) => v
Database.Instance.getDocuments(ids, callback, "newDocuments");
}
+function GetYoutubeApiKey(callback: (result?: string) => void) {
+ // Load client secrets from a local file.
+ fs.readFile('client_secret.json', function processClientSecrets(err: any, content: any) {
+ if (err) {
+ console.log('Error loading client secret file: ' + err);
+ return;
+ }
+ callback(content);
+ });
+}
+
const suffixMap: { [type: string]: (string | [string, string | ((json: any) => any)]) } = {
"number": "_n",
@@ -442,4 +459,5 @@ function CreateField(newValue: any) {
}
server.listen(serverPort);
-console.log(`listening on port ${serverPort}`); \ No newline at end of file
+console.log(`listening on port ${serverPort}`);
+