aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Main.tsx26
-rw-r--r--src/Server.tsx84
-rw-r--r--src/documents/Documents.ts18
-rw-r--r--src/fields/Document.ts71
-rw-r--r--src/fields/Field.ts12
-rw-r--r--src/fields/FieldUpdatedArgs.ts27
-rw-r--r--src/fields/ImageField.ts4
-rw-r--r--src/fields/RichTextField.ts3
-rw-r--r--src/stores/NodeCollectionStore.ts26
-rw-r--r--src/stores/NodeStore.ts24
-rw-r--r--src/stores/StaticTextNodeStore.ts16
-rw-r--r--src/stores/VideoNodeStore.ts17
-rw-r--r--src/util/Scripting.ts4
-rw-r--r--src/views/collections/CollectionDockingView.tsx7
-rw-r--r--src/views/collections/CollectionFreeFormView.tsx41
-rw-r--r--src/views/collections/CollectionViewBase.tsx9
-rw-r--r--src/views/nodes/CollectionFreeFormDocumentView.tsx4
-rw-r--r--src/views/nodes/DocumentView.tsx26
-rw-r--r--src/views/nodes/FieldTextBox.scss14
-rw-r--r--src/views/nodes/FieldView.tsx7
-rw-r--r--src/views/nodes/FormattedTextBox.tsx18
-rw-r--r--src/views/nodes/ImageBox.tsx65
-rw-r--r--src/views/nodes/TextNodeView.tsx28
-rw-r--r--src/views/nodes/TopBar.tsx46
-rw-r--r--src/views/nodes/VideoNodeView.scss5
-rw-r--r--src/views/nodes/VideoNodeView.tsx29
26 files changed, 489 insertions, 142 deletions
diff --git a/src/Main.tsx b/src/Main.tsx
index f0e550f24..6730cf799 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -46,20 +46,20 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
x: 450, y: 500, title: "cat 1"
});
- const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
- x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v
- }));
- schemaDocs[0].SetData(KS.Author, "Tyler", TextField);
- schemaDocs[4].SetData(KS.Author, "Bob", TextField);
- schemaDocs.push(doc2);
- const doc7 = Documents.SchemaDocument(schemaDocs)
- const docset = [doc1, doc2, doc3, doc7];
+ // const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+ // x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v
+ // }));
+ // schemaDocs[0].SetData(KS.Author, "Tyler", TextField);
+ // schemaDocs[4].SetData(KS.Author, "Bob", TextField);
+ // schemaDocs.push(doc2);
+ // const doc7 = Documents.SchemaDocument(schemaDocs)
+ const docset = [doc3]; // [doc1, doc2, doc3, doc7];
let doc4 = Documents.CollectionDocument(docset, {
x: 0, y: 400, title: "mini collection"
});
- let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
- x: 650, y: 500, width: 600, height: 600, title: "cat 2"
- });
+ // let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+ // x: 650, y: 500, width: 600, height: 600, title: "cat 2"
+ // });
let docset2 = new Array<Document>(doc4);//, doc1, doc3);
let doc6 = Documents.CollectionDocument(docset2, {
x: 350, y: 100, width: 600, height: 600, title: "docking collection"
@@ -71,11 +71,11 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
// mainNodes.Data.push(doc6);
// mainNodes.Data.push(doc2);
mainNodes.Data.push(doc4);
- // mainNodes.Data.push(doc3);
+ mainNodes.Data.push(doc3);
// mainNodes.Data.push(doc5);
// mainNodes.Data.push(doc1);
//mainNodes.Data.push(doc2);
- mainNodes.Data.push(doc6);
+ //mainNodes.Data.push(doc6);
mainContainer.Set(KeyStore.Data, mainNodes);
}
//}
diff --git a/src/Server.tsx b/src/Server.tsx
new file mode 100644
index 000000000..2f8135ed0
--- /dev/null
+++ b/src/Server.tsx
@@ -0,0 +1,84 @@
+import { Field, FieldWaiting, FIELD_ID, DOC_ID, FIELD_WAITING } from "./fields/Field"
+import { Key, KeyStore } from "./fields/Key"
+import { ObservableMap, computed, action, observable } from "mobx";
+import { Document } from "./fields/Document"
+import { TextField } from "./fields/TextField";
+
+export class Server {
+ static FieldStore: ObservableMap<FIELD_ID, Field> = new ObservableMap();
+ static DocumentStore: ObservableMap<DOC_ID, ObservableMap<Key, FIELD_ID>> = new ObservableMap();
+ public static ClientFieldsWaiting: ObservableMap<FIELD_ID, boolean> = new ObservableMap();
+ public static ClientFieldsCached: ObservableMap<DOC_ID, Field | FIELD_WAITING> = new ObservableMap();
+
+ public static GetDocument(docid: DOC_ID) {
+ if (this.ClientFieldsCached.has(docid))
+ return this.ClientFieldsCached.get(docid) as Document;
+
+ if (this.DocumentStore.has(docid)) {
+ var clientDoc = new Document(docid);
+ this.cacheFieldInstance(clientDoc);
+ return clientDoc; // bcz: careful ... this assumes the document is on the server. if it's not, the client will have a document with no backing store.
+ }
+ }
+
+ public static AddDocument(document: Document) {
+ this.DocumentStore.set(document.Id, new ObservableMap());
+ document.fields.forEach((field, key) => {
+ this.FieldStore.set((field as Field).Id, (field as Field));
+ this.DocumentStore.get(document.Id)!.set(key, (field as Field).Id);
+ });
+ }
+ public static AddDocumentField(doc: Document, key: Key, value: Field) {
+ if (this.DocumentStore.get(doc.Id))
+ this.DocumentStore.get(doc.Id)!.set(key, value.Id);
+ }
+ public static DeleteDocumentField(doc: Document, key: Key) {
+ if (this.DocumentStore.get(doc.Id))
+ this.DocumentStore.get(doc.Id)!.delete(key);
+ }
+ public static SetFieldValue(field: Field, value: any) {
+ if (this.FieldStore.get(field.Id))
+ this.FieldStore.get(field.Id)!.TrySetValue(value);
+ }
+
+ @action
+ public static GetDocumentField(doc: Document, key: Key) {
+ var fieldid = doc._proxies.get(key);
+ if (!this.ClientFieldsCached.has(fieldid)) {
+ this.ClientFieldsCached.set(fieldid, FieldWaiting);
+
+ // replace this block with appropriate callback-style fetch code from actual server
+ setTimeout(action(() => {
+ var fieldfromserver = this.FieldStore.get(fieldid);
+ this.ClientFieldsWaiting.delete(fieldid);
+ doc._proxies.delete(key);
+ fieldfromserver = this.cacheFieldInstance(fieldfromserver!);
+ doc.fields.set(key, fieldfromserver);
+ }),
+ key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500
+ )
+ }
+ return this.ClientFieldsCached.get(fieldid);
+ }
+ static times = 0; // hack for testing
+
+ @action
+ static cacheFieldInstance(clientField: Field) {
+ var cached = this.ClientFieldsCached.get(clientField.Id);
+ if (!cached || cached == FieldWaiting) {
+ this.ClientFieldsCached.set(clientField.Id, clientField);
+
+ // if the field is a document, then we need to inquire all of its fields from the server...
+ if (clientField instanceof Document) {
+ clientField.Set(KeyStore.Title, new TextField(clientField.Title));
+ setTimeout(action(() => {
+ var clientDocFields = this.DocumentStore.get(clientField.Id);
+ clientDocFields!.forEach((field: FIELD_ID, key: Key) => clientField._proxies.set(key, field));
+ }
+ ),
+ 1500);
+ }
+ }
+ return this.ClientFieldsCached.get(clientField.Id) as Field;
+ }
+}
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index 252ee9766..2ad6b64c3 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -1,4 +1,5 @@
import { Document } from "../fields/Document";
+import { Server } from "../Server";
import { KeyStore } from "../fields/Key";
import { TextField } from "../fields/TextField";
import { NumberField } from "../fields/NumberField";
@@ -7,9 +8,9 @@ import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { CollectionSchemaView } from "../views/collections/CollectionSchemaView";
import { ImageField } from "../fields/ImageField";
-import { RichTextField } from "../fields/RichTextField";
import { ImageBox } from "../views/nodes/ImageBox";
import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView";
+import { FIELD_ID } from "../fields/Field";
interface DocumentOptions {
x?: number;
@@ -106,10 +107,12 @@ export namespace Documents {
}
- let imageProto: Document;
+ let imageProtoId: FIELD_ID;
function GetImagePrototype(): Document {
- if (!imageProto) {
- imageProto = new Document();
+ if (imageProtoId === undefined) {
+ let imageProto = new Document();
+ imageProtoId = imageProto.Id;
+ imageProto.Set(KeyStore.Title, new TextField("IMAGE PROTO"));
imageProto.Set(KeyStore.X, new NumberField(0));
imageProto.Set(KeyStore.Y, new NumberField(0));
imageProto.Set(KeyStore.Width, new NumberField(300));
@@ -117,15 +120,18 @@ export namespace Documents {
imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString()));
// imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />'));
imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
+ Server.AddDocument(imageProto);
+ return imageProto;
}
- return imageProto;
+ return Server.GetDocument(imageProtoId)!;
}
export function ImageDocument(url: string, options: DocumentOptions = {}): Document {
let doc = GetImagePrototype().MakeDelegate();
setupOptions(doc, options);
doc.Set(KeyStore.Data, new ImageField(new URL(url)));
- return doc;
+ Server.AddDocument(doc);
+ return Server.GetDocument(doc.Id)!;
}
let collectionProto: Document;
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 742149a03..3d74c047c 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -1,17 +1,19 @@
-import { Field, Cast, Opt } from "./Field"
+import { Field, Cast, Opt, FieldWaiting, FIELD_ID, DOC_ID } from "./Field"
import { Key, KeyStore } from "./Key"
import { NumberField } from "./NumberField";
-import { ObservableMap, computed } from "mobx";
+import { ObservableMap, computed, action, observable } from "mobx";
import { TextField } from "./TextField";
import { ListField } from "./ListField";
+import { findDOMNode } from "react-dom";
+import { Server } from "../Server";
export class Document extends Field {
- private fields: ObservableMap<Key, Field> = new ObservableMap();
+ public fields: ObservableMap<Key, Opt<Field>> = new ObservableMap();
+ public _proxies: ObservableMap<Key, FIELD_ID> = new ObservableMap();
- static _untitledDocName = "<untitled>";
@computed
public get Title() {
- return this.GetData(KeyStore.Title, TextField, Document._untitledDocName);
+ return this.GetText(KeyStore.Title, "<untitled>");
}
Get(key: Key, ignoreProto: boolean = false): Opt<Field> {
@@ -19,15 +21,25 @@ export class Document extends Field {
if (ignoreProto) {
if (this.fields.has(key)) {
field = this.fields.get(key);
+ } else if (this._proxies.has(key)) {
+ field = Server.GetDocumentField(this, key);
}
} else {
let doc: Opt<Document> = this;
- while (doc && !(doc.fields.has(key))) {
- doc = doc.GetPrototype();
- }
-
- if (doc) {
- field = doc.fields.get(key);
+ while (doc && doc != FieldWaiting && field != FieldWaiting) {
+ if (!doc.fields.has(key)) {
+ if (doc._proxies.has(key)) {
+ field = Server.GetDocumentField(doc, key);
+ break;
+ }
+ if ((doc.fields.has(KeyStore.Prototype) || doc._proxies.has(KeyStore.Prototype))) {
+ doc = doc.GetPrototype();
+ } else
+ break;
+ } else {
+ field = doc.fields.get(key);
+ break;
+ }
}
}
@@ -35,12 +47,16 @@ export class Document extends Field {
}
GetT<T extends Field = Field>(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): Opt<T> {
- return Cast(this.Get(key, ignoreProto), ctor);
+ var getfield = this.Get(key, ignoreProto);
+ if (getfield != FieldWaiting) {
+ return Cast(getfield, ctor);
+ }
+ return FieldWaiting;
}
GetOrCreate<T extends Field>(key: Key, ctor: { new(): T }, ignoreProto: boolean = false): T {
const field = this.GetT(key, ctor, ignoreProto);
- if (field) {
+ if (field && field != FieldWaiting) {
return field;
}
const newField = new ctor();
@@ -66,46 +82,39 @@ export class Document extends Field {
return this.GetData<T[], ListField<T>>(key, ListField, defaultVal)
}
+ @action
Set(key: Key, field: Field | undefined): void {
if (field) {
this.fields.set(key, field);
+ Server.AddDocumentField(this, key, field);
} else {
this.fields.delete(key);
+ Server.DeleteDocumentField(this, key);
}
}
+ @action
SetData<T, U extends Field & { Data: T }>(key: Key, value: T, ctor: { new(): U }, replaceWrongType = true) {
+
let field = this.Get(key, true);
+ //if (field != WAITING) { // do we want to wait for the field to come back from the server to set it, or do we overwrite?
if (field instanceof ctor) {
field.Data = value;
+ Server.SetFieldValue(field, value);
} else if (!field || replaceWrongType) {
let newField = new ctor();
newField.Data = value;
this.Set(key, newField);
}
+ //}
}
- SetVal<T extends Field>(key: Key, value: any, ctor: { new(): T }, replaceWrongType = true): boolean {
- let field = this.Get(key, true);
- if (field != null) {
- return field.TrySetValue(value);
- } else if (field && replaceWrongType) {
- field = new ctor();
- if (field.TrySetValue(value)) {
- this.Set(key, field);
- return true;
- } else {
- return false;
- }
- } else {
- return false;
- }
- }
-
+ @action
SetText(key: Key, value: string, replaceWrongType = true) {
this.SetData(key, value, TextField, replaceWrongType);
}
+ @action
SetNumber(key: Key, value: number, replaceWrongType = true) {
this.SetData(key, value, NumberField, replaceWrongType);
}
@@ -117,7 +126,7 @@ export class Document extends Field {
GetAllPrototypes(): Document[] {
let protos: Document[] = [];
let doc: Opt<Document> = this;
- while (doc != null) {
+ while (doc && doc != FieldWaiting) {
protos.push(doc);
doc = doc.GetPrototype();
}
diff --git a/src/fields/Field.ts b/src/fields/Field.ts
index 1453e52a4..9880116c0 100644
--- a/src/fields/Field.ts
+++ b/src/fields/Field.ts
@@ -10,17 +10,21 @@ export function Cast<T extends Field>(field: Opt<Field>, ctor: { new(): T }): Op
return undefined;
}
-export type Opt<T> = T | undefined;
+export let FieldWaiting: FIELD_WAITING = "<Waiting>";
+export type FIELD_WAITING = "<Waiting>";
+export type FIELD_ID = string | undefined;
+export type DOC_ID = FIELD_ID;
+export type Opt<T> = T | undefined | FIELD_WAITING;
export abstract class Field {
//FieldUpdated: TypedEvent<Opt<FieldUpdatedArgs>> = new TypedEvent<Opt<FieldUpdatedArgs>>();
- private id: string;
- get Id(): string {
+ private id: FIELD_ID;
+ get Id(): FIELD_ID {
return this.id;
}
- constructor(id: Opt<string> = undefined) {
+ constructor(id: FIELD_ID = undefined) {
this.id = id || Utils.GenerateGuid();
}
diff --git a/src/fields/FieldUpdatedArgs.ts b/src/fields/FieldUpdatedArgs.ts
new file mode 100644
index 000000000..23ccf2a5a
--- /dev/null
+++ b/src/fields/FieldUpdatedArgs.ts
@@ -0,0 +1,27 @@
+import { Field, Opt } from "./Field";
+import { Document } from "./Document";
+import { Key } from "./Key";
+
+export enum FieldUpdatedAction {
+ Add,
+ Remove,
+ Replace,
+ Update
+}
+
+export interface FieldUpdatedArgs {
+ field: Field;
+ action: FieldUpdatedAction;
+}
+
+export interface DocumentUpdatedArgs {
+ field: Document;
+ key: Key;
+
+ oldValue: Opt<Field>;
+ newValue: Opt<Field>;
+
+ fieldArgs?: FieldUpdatedArgs;
+
+ action: FieldUpdatedAction;
+}
diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts
index 9bfacf231..bc2e7cdf4 100644
--- a/src/fields/ImageField.ts
+++ b/src/fields/ImageField.ts
@@ -2,8 +2,8 @@ import { BasicField } from "./BasicField";
import { Field } from "./Field";
export class ImageField extends BasicField<URL> {
- constructor(data: URL) {
- super(data);
+ constructor(data: URL | undefined = undefined) {
+ super(data == undefined ? new URL("http://cs.brown.edu/~bcz/face.gif") : data);
}
toString(): string {
diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts
index 4cf5c99a7..24c7472d8 100644
--- a/src/fields/RichTextField.ts
+++ b/src/fields/RichTextField.ts
@@ -1,12 +1,11 @@
import { BasicField } from "./BasicField";
-import { Field } from "./Field";
export class RichTextField extends BasicField<string> {
constructor(data: string = "") {
super(data);
}
- Copy(): Field {
+ Copy() {
return new RichTextField(this.Data);
}
diff --git a/src/stores/NodeCollectionStore.ts b/src/stores/NodeCollectionStore.ts
new file mode 100644
index 000000000..7fac83d51
--- /dev/null
+++ b/src/stores/NodeCollectionStore.ts
@@ -0,0 +1,26 @@
+import { computed, observable, action } from "mobx";
+import { NodeStore } from "./NodeStore";
+import { Document } from "../fields/Document";
+
+export class NodeCollectionStore extends NodeStore {
+
+ @observable
+ public Scale: number = 1;
+
+ @observable
+ public Nodes: NodeStore[] = new Array<NodeStore>();
+
+ @observable
+ public Docs: Document[] = [];
+
+ @computed
+ public get Transform(): string {
+ const halfWidth = window.innerWidth / 2, halfHeight = window.innerHeight / 2;
+ return `translate(${this.X + halfWidth}px, ${this.Y + halfHeight}px) scale(${this.Scale}) translate(${-halfWidth}px, ${-halfHeight}px)`;
+ }
+
+ @action
+ public AddNodes(stores: NodeStore[]): void {
+ stores.forEach(store => this.Nodes.push(store));
+ }
+} \ No newline at end of file
diff --git a/src/stores/NodeStore.ts b/src/stores/NodeStore.ts
new file mode 100644
index 000000000..6a734cf44
--- /dev/null
+++ b/src/stores/NodeStore.ts
@@ -0,0 +1,24 @@
+import { computed, observable } from "mobx";
+import { Utils } from "../Utils";
+
+export class NodeStore {
+
+ public Id: string = Utils.GenerateGuid();
+
+ @observable
+ public X: number = 0;
+
+ @observable
+ public Y: number = 0;
+
+ @observable
+ public Width: number = 0;
+
+ @observable
+ public Height: number = 0;
+
+ @computed
+ public get Transform(): string {
+ return "translate(" + this.X + "px, " + this.Y + "px)";
+ }
+} \ No newline at end of file
diff --git a/src/stores/StaticTextNodeStore.ts b/src/stores/StaticTextNodeStore.ts
new file mode 100644
index 000000000..7c342a7a2
--- /dev/null
+++ b/src/stores/StaticTextNodeStore.ts
@@ -0,0 +1,16 @@
+import { observable } from "mobx";
+import { NodeStore } from "./NodeStore";
+
+export class StaticTextNodeStore extends NodeStore {
+
+ constructor(initializer: Partial<StaticTextNodeStore>) {
+ super();
+ Object.assign(this, initializer);
+ }
+
+ @observable
+ public Title: string = "";
+
+ @observable
+ public Text: string = "";
+} \ No newline at end of file
diff --git a/src/stores/VideoNodeStore.ts b/src/stores/VideoNodeStore.ts
new file mode 100644
index 000000000..e5187ab07
--- /dev/null
+++ b/src/stores/VideoNodeStore.ts
@@ -0,0 +1,17 @@
+import { observable } from "mobx";
+import { NodeStore } from "./NodeStore";
+
+export class VideoNodeStore extends NodeStore {
+
+ constructor(initializer: Partial<VideoNodeStore>) {
+ super();
+ Object.assign(this, initializer);
+ }
+
+ @observable
+ public Title: string = "";
+
+ @observable
+ public Url: string = "";
+
+} \ No newline at end of file
diff --git a/src/util/Scripting.ts b/src/util/Scripting.ts
index 94339e7fe..804c67bc5 100644
--- a/src/util/Scripting.ts
+++ b/src/util/Scripting.ts
@@ -1,6 +1,6 @@
// import * as ts from "typescript"
let ts = (window as any).ts;
-import { Opt, Field } from "../fields/Field";
+import { Opt, Field, FieldWaiting } from "../fields/Field";
import { Document as DocumentImport } from "../fields/Document";
import { NumberField as NumberFieldImport } from "../fields/NumberField";
import { TextField as TextFieldImport } from "../fields/TextField";
@@ -14,7 +14,7 @@ export interface ExecutableScript {
}
function ExecScript(script: string, diagnostics: Opt<any[]>): ExecutableScript {
- const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
+ const compiled = !(diagnostics && diagnostics != FieldWaiting && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error));
let func: () => Opt<Field>;
if (compiled) {
diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx
index de68cf0d7..e489e319a 100644
--- a/src/views/collections/CollectionDockingView.tsx
+++ b/src/views/collections/CollectionDockingView.tsx
@@ -15,6 +15,7 @@ import * as GoldenLayout from "golden-layout";
import * as ReactDOM from 'react-dom';
import { DragManager } from "../../util/DragManager";
import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
+import { FieldWaiting } from "../../fields/Field";
@observer
export class CollectionDockingView extends CollectionViewBase {
@@ -69,6 +70,8 @@ export class CollectionDockingView extends CollectionViewBase {
@action
onResize = (event: any) => {
+ if (this.props.ContainingDocumentView == FieldWaiting)
+ return;
var cur = this.props.ContainingDocumentView!.MainContent.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
@@ -217,7 +220,7 @@ export class CollectionDockingView extends CollectionViewBase {
CollectionDockingView.myLayout._maximizedStack = stack;
CollectionDockingView.myLayout._maxstack = stack.header.controlsContainer.find('.lm_maximise');
}
- stack.header.controlsContainer.find('.lm_popout').hide();
+ //stack.header.controlsContainer.find('.lm_popout').hide();
stack.header.controlsContainer.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
.click(function () {
@@ -252,6 +255,8 @@ export class CollectionDockingView extends CollectionViewBase {
render() {
+ if (this.props.ContainingDocumentView == FieldWaiting)
+ return;
const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props;
const value: Document[] = Document.GetData(fieldKey, ListField, []);
// bcz: not sure why, but I need these to force the flexlayout to update when the collection size changes.
diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx
index c07a7e563..45d37ca4f 100644
--- a/src/views/collections/CollectionFreeFormView.tsx
+++ b/src/views/collections/CollectionFreeFormView.tsx
@@ -13,6 +13,7 @@ import "./CollectionFreeFormView.scss";
import { Utils } from "../../Utils";
import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase";
import { SelectionManager } from "../../util/SelectionManager";
+import { FieldWaiting } from "../../fields/Field";
@observer
export class CollectionFreeFormView extends CollectionViewBase {
@@ -32,21 +33,23 @@ export class CollectionFreeFormView extends CollectionViewBase {
const doc = de.data["document"];
var me = this;
if (doc instanceof CollectionFreeFormDocumentView) {
- if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) {
+ if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this && doc.props.ContainingCollectionView != FieldWaiting) {
doc.props.ContainingCollectionView.removeDocument(doc.props.Document);
this.addDocument(doc.props.Document);
}
const xOffset = de.data["xOffset"] as number || 0;
const yOffset = de.data["yOffset"] as number || 0;
const { scale, translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!);
- let sscale = this.props.ContainingDocumentView!.props.Document.GetData(KeyStore.Scale, NumberField, Number(1))
- const screenX = de.x - xOffset;
- const screenY = de.y - yOffset;
- const docX = (screenX - translateX) / sscale / scale;
- const docY = (screenY - translateY) / sscale / scale;
- doc.x = docX;
- doc.y = docY;
- this.bringToFront(doc);
+ if (this.props.ContainingDocumentView != FieldWaiting) {
+ let sscale = this.props.ContainingDocumentView!.props.Document.GetData(KeyStore.Scale, NumberField, Number(1))
+ const screenX = de.x - xOffset;
+ const screenY = de.y - yOffset;
+ const docX = (screenX - translateX) / sscale / scale;
+ const docY = (screenY - translateY) / sscale / scale;
+ doc.x = docX;
+ doc.y = docY;
+ this.bringToFront(doc);
+ }
}
e.stopPropagation();
}
@@ -61,12 +64,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
}
- downactive: boolean = false;
@action
onPointerDown = (e: React.PointerEvent): void => {
- var me = this;
- me.downactive = this.active;
- var title = this.props.DocumentForCollection.Title;
if ((e.button === 2 && this.active) ||
!e.defaultPrevented) {
document.removeEventListener("pointermove", this.onPointerMove);
@@ -89,9 +88,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
onPointerMove = (e: PointerEvent): void => {
var me = this;
- var act = me.active;
- var title = me.props.DocumentForCollection.Title;
- if (!e.cancelBubble && this.active) {
+ if (!e.cancelBubble && this.active && this.props.ContainingDocumentView != FieldWaiting) {
e.preventDefault();
e.stopPropagation();
let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace;
@@ -108,6 +105,8 @@ export class CollectionFreeFormView extends CollectionViewBase {
onPointerWheel = (e: React.WheelEvent): void => {
e.stopPropagation();
+ if (this.props.ContainingDocumentView == FieldWaiting)
+ return;
let { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY } = this.props.ContainingDocumentView!.TransformToLocalPoint(e.pageX, e.pageY);
var deltaScale = (1 - (e.deltaY / 1000)) * Ss;
@@ -142,11 +141,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
x: x, y: y
})
let docs = that.props.DocumentForCollection.GetT(KeyStore.Data, ListField);
- if (!docs) {
- docs = new ListField<Document>();
- that.props.DocumentForCollection.Set(KeyStore.Data, docs)
+ if (docs != FieldWaiting) {
+ if (!docs) {
+ docs = new ListField<Document>();
+ that.props.DocumentForCollection.Set(KeyStore.Data, docs)
+ }
+ docs.Data.push(doc);
}
- docs.Data.push(doc);
}
}), false)
diff --git a/src/views/collections/CollectionViewBase.tsx b/src/views/collections/CollectionViewBase.tsx
index 35d938d43..4fce02ef6 100644
--- a/src/views/collections/CollectionViewBase.tsx
+++ b/src/views/collections/CollectionViewBase.tsx
@@ -1,7 +1,7 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../fields/Document";
-import { Opt } from "../../fields/Field";
+import { Opt, FieldWaiting } from "../../fields/Field";
import { Key, KeyStore } from "../../fields/Key";
import { ListField } from "../../fields/ListField";
import { SelectionManager } from "../../util/SelectionManager";
@@ -30,9 +30,10 @@ export class CollectionViewBase extends React.Component<CollectionViewProps> {
public get active(): boolean {
var isSelected = (this.props.ContainingDocumentView instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.ContainingDocumentView));
var childSelected = SelectionManager.SelectedDocuments().some(view => view.props.ContainingCollectionView == this);
- var topMost = this.props.ContainingDocumentView != undefined && (
- this.props.ContainingDocumentView.props.ContainingCollectionView == undefined ||
- this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView);
+ var topMost = this.props.ContainingDocumentView != undefined &&
+ this.props.ContainingDocumentView != FieldWaiting && this.props.ContainingDocumentView.props.ContainingCollectionView != FieldWaiting && (
+ this.props.ContainingDocumentView.props.ContainingCollectionView == undefined ||
+ this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView);
return isSelected || childSelected || topMost;
}
@action
diff --git a/src/views/nodes/CollectionFreeFormDocumentView.tsx b/src/views/nodes/CollectionFreeFormDocumentView.tsx
index 23863ce18..25d67d96a 100644
--- a/src/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -10,6 +10,7 @@ import { ContextMenu } from "../ContextMenu";
import "./NodeView.scss";
import React = require("react");
import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { FieldWaiting } from "../../fields/Field";
@observer
@@ -84,7 +85,8 @@ export class CollectionFreeFormDocumentView extends DocumentView {
@computed
get active(): boolean {
- return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined || this.props.ContainingCollectionView!.active;
+ return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined ||
+ (this.props.ContainingCollectionView != FieldWaiting && this.props.ContainingCollectionView!.active);
}
@computed
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index 5be17fe54..81353cd60 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -1,7 +1,7 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../fields/Document";
-import { Opt } from "../../fields/Field";
+import { Opt, FieldWaiting } from "../../fields/Field";
import { Key, KeyStore } from "../../fields/Key";
import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
@@ -49,7 +49,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
//
@computed
public get ScalingToScreenSpace(): number {
- if (this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) {
+ if (this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView != FieldWaiting &&
+ this.props.ContainingCollectionView.props.ContainingDocumentView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != FieldWaiting) {
let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1));
return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss;
}
@@ -62,7 +63,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
public TransformToLocalPoint(screenX: number, screenY: number) {
// if this collection view is nested within another collection view, then
// first transform the screen point into the parent collection's coordinate space.
- let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ?
+ let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView != FieldWaiting &&
+ this.props.ContainingCollectionView.props.ContainingDocumentView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != FieldWaiting ?
this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) :
{ LocalX: screenX, LocalY: screenY };
let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH;
@@ -111,8 +113,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
// if this collection view is nested within another collection view, then
// first transform the local point into the parent collection's coordinate space.
- let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined;
- if (containingDocView != undefined) {
+ let containingDocView = this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView != FieldWaiting ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined;
+ if (containingDocView != undefined && containingDocView != FieldWaiting) {
let ss = containingDocView.props.Document.GetData(KeyStore.Scale, NumberField, Number(1));
let panxx = containingDocView.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
let panyy = containingDocView.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)) + COLLECTION_BORDER_WIDTH * ss;
@@ -125,18 +127,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
render() {
- let doc = this.props.Document;
let bindings = { ...this.props } as any;
for (const key of this.layoutKeys) {
- bindings[key.Name + "Key"] = key;
+ bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
- if (bindings.DocumentView === undefined)
- bindings.DocumentView = this;
for (const key of this.layoutFields) {
- let field = doc.Get(key);
- if (field) {
- bindings[key.Name] = field.GetValue();
- }
+ let field = this.props.Document.Get(key);
+ bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ }
+ if (bindings.DocumentView === undefined) {
+ bindings.DocumentView = this; // set the DocumentView to this if it hasn't already been set by a sub-class during its render method.
}
return (
<div className="node" ref={this._mainCont} style={{ width: "100%", height: "100%", }}>
diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss
new file mode 100644
index 000000000..b6ce2fabc
--- /dev/null
+++ b/src/views/nodes/FieldTextBox.scss
@@ -0,0 +1,14 @@
+.ProseMirror {
+ margin-top: -1em;
+ width: 100%;
+ height: 100%;
+}
+
+.ProseMirror:focus {
+ outline: none !important
+}
+
+.fieldTextBox-cont {
+ background: white;
+ padding: 1vw;
+} \ No newline at end of file
diff --git a/src/views/nodes/FieldView.tsx b/src/views/nodes/FieldView.tsx
index 71e24986a..05a7b91b9 100644
--- a/src/views/nodes/FieldView.tsx
+++ b/src/views/nodes/FieldView.tsx
@@ -2,7 +2,7 @@ import React = require("react")
import { Document } from "../../fields/Document";
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Field, Opt } from "../../fields/Field";
+import { Field, Opt, FieldWaiting } from "../../fields/Field";
import { TextField } from "../../fields/TextField";
import { NumberField } from "../../fields/NumberField";
import { RichTextField } from "../../fields/RichTextField";
@@ -47,9 +47,10 @@ export class FieldView extends React.Component<FieldViewProps> {
}
else if (field instanceof NumberField) {
return <p>{field.Data}</p>
- } else {
+ } else if (field != FieldWaiting) {
return <p>{field.GetValue}</p>
- }
+ } else
+ return <p> {"Waiting for server..."} </p>
}
} \ No newline at end of file
diff --git a/src/views/nodes/FormattedTextBox.tsx b/src/views/nodes/FormattedTextBox.tsx
index e00ab6701..3e3e22e46 100644
--- a/src/views/nodes/FormattedTextBox.tsx
+++ b/src/views/nodes/FormattedTextBox.tsx
@@ -1,11 +1,12 @@
import { action, IReactionDisposer, reaction } from "mobx";
+import { observer } from "mobx-react"
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { schema } from "prosemirror-schema-basic";
import { EditorState, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Opt } from "../../fields/Field";
+import { Opt, FieldWaiting } from "../../fields/Field";
import { SelectionManager } from "../../util/SelectionManager";
import "./FormattedTextBox.scss";
import React = require("react")
@@ -29,7 +30,8 @@ import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView
// When rendered() by React, this extracts the TextController from the Document stored at the
// specified Key and assigns it to an HTML input node. When changes are made tot his node,
// this will edit the document and assign the new value to that field.
-//
+//]
+@observer
export class FormattedTextBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); }
@@ -46,7 +48,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
}
dispatchTransaction = (tx: Transaction) => {
- if (this._editorView) {
+ if (this._editorView && this._editorView != FieldWaiting) {
const state = this._editorView.state.apply(tx);
this._editorView.updateState(state);
const { doc, fieldKey } = this.props;
@@ -67,7 +69,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
};
let field = doc.GetT(fieldKey, RichTextField);
- if (field) {
+ if (field && field != FieldWaiting) { // bcz: don't think this works
state = EditorState.fromJSON(config, JSON.parse(field.Data));
} else {
state = EditorState.create(config);
@@ -81,19 +83,19 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this._reactionDisposer = reaction(() => {
const field = this.props.doc.GetT(this.props.fieldKey, RichTextField);
- return field ? field.Data : undefined;
+ return field && field != FieldWaiting ? field.Data : undefined;
}, (field) => {
- if (field && this._editorView) {
+ if (field && this._editorView && this._editorView != FieldWaiting) {
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)));
}
})
}
componentWillUnmount() {
- if (this._editorView) {
+ if (this._editorView && this._editorView != FieldWaiting) {
this._editorView.destroy();
}
- if (this._reactionDisposer) {
+ if (this._reactionDisposer && this._reactionDisposer != FieldWaiting) {
this._reactionDisposer();
}
}
diff --git a/src/views/nodes/ImageBox.tsx b/src/views/nodes/ImageBox.tsx
index eceb04b07..123c76d19 100644
--- a/src/views/nodes/ImageBox.tsx
+++ b/src/views/nodes/ImageBox.tsx
@@ -7,19 +7,20 @@ import React = require("react")
import { ImageField } from '../../fields/ImageField';
import { FieldViewProps, FieldView } from './FieldView';
import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView';
+import { FieldWaiting } from '../../fields/Field';
+import { observer } from "mobx-react"
+import { observable, action } from 'mobx';
-interface ImageBoxState {
- photoIndex: number,
- isOpen: boolean,
-};
-
-export class ImageBox extends React.Component<FieldViewProps, ImageBoxState> {
+@observer
+export class ImageBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString("ImageBox"); }
private _ref: React.RefObject<HTMLDivElement>;
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
+ @observable private _photoIndex: number = 0;
+ @observable private _isOpen: boolean = false;
constructor(props: FieldViewProps) {
super(props);
@@ -50,42 +51,42 @@ export class ImageBox extends React.Component<FieldViewProps, ImageBoxState> {
this._lastTap = Date.now();
}
}
+ @action
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointerup", this.onPointerUp);
if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
- this.setState({ isOpen: true })
+ this._isOpen = true;
}
e.stopPropagation();
}
- render() {
- let field = this.props.doc.GetT(this.props.fieldKey, ImageField);
- let path = "";
- if (field) {
- path = field.Data.href;
- }
- const images = [path,];
- var lightbox = () => {
- const { photoIndex } = this.state;
- if (this.state.isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
- return (<Lightbox
- mainSrc={images[photoIndex]}
- nextSrc={photoIndex + 1 < images.length ? images[(photoIndex + 1) % images.length] : undefined}
- prevSrc={photoIndex - 1 > 0 ? images[(photoIndex + images.length - 1) % images.length] : undefined}
- onCloseRequest={() => this.setState({ isOpen: false })}
- onMovePrevRequest={() =>
- this.setState({ photoIndex: (photoIndex + images.length - 1) % images.length, })
- }
- onMoveNextRequest={() =>
- this.setState({ photoIndex: (photoIndex + 1) % images.length, })
- }
- />)
- }
+ lightbox = (path: string) => {
+ const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
+ if (this._isOpen && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) {
+ return (<Lightbox
+ mainSrc={images[this._photoIndex]}
+ nextSrc={images[(this._photoIndex + 1) % images.length]}
+ prevSrc={images[(this._photoIndex + images.length - 1) % images.length]}
+ onCloseRequest={() => this.setState({ isOpen: false })}
+ onMovePrevRequest={action(() =>
+ this._photoIndex = (this._photoIndex + images.length - 1) % images.length
+ )}
+ onMoveNextRequest={action(() =>
+ this._photoIndex = (this._photoIndex + 1) % images.length
+ )}
+ />)
}
+ }
+
+ render() {
+ let field = this.props.doc.Get(this.props.fieldKey);
+ let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
+
return (
<div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} >
- <img src={images[0]} width="100%" alt="Image not found" />
- {lightbox()}
+ <img src={path} width="100%" alt="Image not found" />
+ {this.lightbox(path)}
</div>)
}
} \ No newline at end of file
diff --git a/src/views/nodes/TextNodeView.tsx b/src/views/nodes/TextNodeView.tsx
new file mode 100644
index 000000000..ab762df12
--- /dev/null
+++ b/src/views/nodes/TextNodeView.tsx
@@ -0,0 +1,28 @@
+import {observer} from "mobx-react";
+import {StaticTextNodeStore} from "../../stores/StaticTextNodeStore";
+import "./NodeView.scss";
+import {TopBar} from "./TopBar";
+import React = require("react");
+
+interface IProps {
+ store: StaticTextNodeStore;
+}
+
+@observer
+export class TextNodeView extends React.Component<IProps> {
+
+ render() {
+ let store = this.props.store;
+ return (
+ <div className="node text-node" style={{transform: store.Transform}}>
+ <TopBar store={store} />
+ <div className="scroll-box">
+ <div className="content">
+ <h3 className="title">{store.Title}</h3>
+ <p className="paragraph">{store.Text}</p>
+ </div>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/views/nodes/TopBar.tsx b/src/views/nodes/TopBar.tsx
new file mode 100644
index 000000000..bb126e8b5
--- /dev/null
+++ b/src/views/nodes/TopBar.tsx
@@ -0,0 +1,46 @@
+import { observer } from "mobx-react";
+import { NodeStore } from "../../stores/NodeStore";
+import "./NodeView.scss";
+import React = require("react");
+
+interface IProps {
+ store: NodeStore;
+}
+
+@observer
+export class TopBar extends React.Component<IProps> {
+
+ private _isPointerDown = false;
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = true;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = false;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (!this._isPointerDown) {
+ return;
+ }
+ this.props.store.X += e.movementX;
+ this.props.store.Y += e.movementY;
+ }
+
+ render() {
+ return <div className="top" onPointerDown={this.onPointerDown}></div>
+ }
+}
diff --git a/src/views/nodes/VideoNodeView.scss b/src/views/nodes/VideoNodeView.scss
new file mode 100644
index 000000000..f412c3519
--- /dev/null
+++ b/src/views/nodes/VideoNodeView.scss
@@ -0,0 +1,5 @@
+.node {
+ video {
+ width: 100%;
+ }
+} \ No newline at end of file
diff --git a/src/views/nodes/VideoNodeView.tsx b/src/views/nodes/VideoNodeView.tsx
new file mode 100644
index 000000000..0a7b3d174
--- /dev/null
+++ b/src/views/nodes/VideoNodeView.tsx
@@ -0,0 +1,29 @@
+import { observer } from "mobx-react";
+import { VideoNodeStore } from "../../stores/VideoNodeStore";
+import "./NodeView.scss";
+import { TopBar } from "./TopBar";
+import "./VideoNodeView.scss";
+import React = require("react");
+
+interface IProps {
+ store: VideoNodeStore;
+}
+
+@observer
+export class VideoNodeView extends React.Component<IProps> {
+
+ render() {
+ let store = this.props.store;
+ return (
+ <div className="node text-node" style={{ transform: store.Transform }}>
+ <TopBar store={store} />
+ <div className="scroll-box">
+ <div className="content">
+ <h3 className="title">{store.Title}</h3>
+ <video src={store.Url} controls />
+ </div>
+ </div>
+ </div>
+ );
+ }
+} \ No newline at end of file