diff options
Diffstat (limited to 'src')
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 |