From 090093a50397ddc2a8fc2c50f5097f4a4ea8a74c Mon Sep 17 00:00:00 2001 From: Hannah Date: Thu, 7 Feb 2019 15:50:23 -0500 Subject: started adding editable schema cells --- src/fields/Document.ts | 4 ++++ src/fields/Field.ts | 2 ++ src/fields/NumberField.ts | 4 ++++ 3 files changed, 10 insertions(+) (limited to 'src/fields') diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 3d74c047c..53138eda9 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -141,6 +141,10 @@ export class Document extends Field { return delegate; } + ToScriptString(): string { + return ""; + } + TrySetValue(value: any): boolean { throw new Error("Method not implemented."); } diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 9880116c0..20d8bf5ed 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -47,6 +47,8 @@ export abstract class Field { return this.id === other.id; } + abstract ToScriptString(): string; + abstract TrySetValue(value: any): boolean; abstract GetValue(): any; diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index c3444f644..03926d696 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -5,6 +5,10 @@ export class NumberField extends BasicField { super(data); } + ToScriptString(): string { + return "new NumberField(this.Data)"; + } + Copy() { return new NumberField(this.Data); } -- cgit v1.2.3-70-g09d2 From f2f9261c6469ed330ff9395fb97e15ca53c891f2 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 7 Feb 2019 16:07:29 -0500 Subject: final cleanup pass. --- src/Server.tsx | 8 ++++---- src/SocketStub.tsx | 2 +- src/fields/Document.ts | 2 +- src/fields/Field.ts | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) (limited to 'src/fields') diff --git a/src/Server.tsx b/src/Server.tsx index 83f0d0f43..7eeec25e1 100644 --- a/src/Server.tsx +++ b/src/Server.tsx @@ -1,11 +1,11 @@ -import { Field, FieldWaiting, FIELD_ID, DOC_ID, FIELD_WAITING } from "./fields/Field" +import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING } from "./fields/Field" import { Key, KeyStore } from "./fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "./fields/Document" import { SocketStub } from "./SocketStub"; export class Server { - private static ClientFieldsCached: ObservableMap = new ObservableMap(); + private static ClientFieldsCached: ObservableMap = new ObservableMap(); // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). // Call this is from within a reaction and test whether the return value is FieldWaiting. @@ -25,14 +25,14 @@ export class Server { static times = 0; // hack for testing public static GetDocumentField(doc: Document, key: Key) { - var timeoutHack: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; + var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; var field = this.GetField(doc._proxies.get(key), action((fieldfromserver: Field) => { doc._proxies.delete(key); doc.fields.set(key, this.cacheField(fieldfromserver)); }) - , timeoutHack); + , hackTimeout); if (field != FieldWaiting) { doc._proxies.delete(key); // perhaps another document inquired the same field } diff --git a/src/SocketStub.tsx b/src/SocketStub.tsx index 49a847d34..f9d48c067 100644 --- a/src/SocketStub.tsx +++ b/src/SocketStub.tsx @@ -1,4 +1,4 @@ -import { Field, FieldWaiting, FIELD_ID, DOC_ID, FIELD_WAITING } from "./fields/Field" +import { Field, FIELD_ID } from "./fields/Field" import { Key, KeyStore } from "./fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "./fields/Document" diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 3d74c047c..cc81ff4a7 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,4 +1,4 @@ -import { Field, Cast, Opt, FieldWaiting, FIELD_ID, DOC_ID } from "./Field" +import { Field, Cast, Opt, FieldWaiting, FIELD_ID } from "./Field" import { Key, KeyStore } from "./Key" import { NumberField } from "./NumberField"; import { ObservableMap, computed, action, observable } from "mobx"; diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 9880116c0..3c1e976d4 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -13,7 +13,6 @@ export function Cast(field: Opt, ctor: { new(): T }): Op export let FieldWaiting: FIELD_WAITING = ""; export type FIELD_WAITING = ""; export type FIELD_ID = string | undefined; -export type DOC_ID = FIELD_ID; export type Opt = T | undefined | FIELD_WAITING; export abstract class Field { -- cgit v1.2.3-70-g09d2 From 9b942166ca6b09ce4f310928c3daf8503a63ee5c Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Thu, 7 Feb 2019 23:33:44 -0500 Subject: Restored Opt and made a new FieldValue type to replace old Opt --- src/Main.tsx | 16 +++++++-------- src/Server.tsx | 10 ++++----- src/fields/Document.ts | 14 ++++++------- src/fields/DocumentReference.ts | 11 ++++++---- src/fields/Field.ts | 13 ++++++------ src/fields/ImageField.ts | 4 ++++ src/fields/Key.ts | 3 +++ src/fields/ListField.ts | 4 ++++ src/fields/RichTextField.ts | 4 ++++ src/fields/TextField.ts | 4 ++++ src/util/Scripting.ts | 17 +++++++++++---- src/views/EditableView.tsx | 9 +++++--- src/views/collections/CollectionDockingView.tsx | 5 ----- src/views/collections/CollectionFreeFormView.tsx | 24 +++++++++------------- src/views/collections/CollectionSchemaView.tsx | 8 +++++++- src/views/collections/CollectionViewBase.tsx | 9 ++++---- src/views/nodes/CollectionFreeFormDocumentView.tsx | 3 +-- src/views/nodes/DocumentView.tsx | 12 +++++------ src/views/nodes/FieldView.tsx | 4 ++-- src/views/nodes/FormattedTextBox.tsx | 10 ++++----- 20 files changed, 107 insertions(+), 77 deletions(-) (limited to 'src/fields') diff --git a/src/Main.tsx b/src/Main.tsx index 6730cf799..a9ed1a365 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -46,14 +46,14 @@ 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 = [doc3]; // [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 = [doc1, doc2, doc3, doc7]; let doc4 = Documents.CollectionDocument(docset, { x: 0, y: 400, title: "mini collection" }); diff --git a/src/Server.tsx b/src/Server.tsx index 04473424a..08e400619 100644 --- a/src/Server.tsx +++ b/src/Server.tsx @@ -1,4 +1,4 @@ -import { Field, FieldWaiting, FIELD_ID, DOC_ID, FIELD_WAITING } from "./fields/Field" +import { Field, FieldWaiting, FIELD_ID, DOC_ID, FIELD_WAITING, FieldValue } from "./fields/Field" import { Key, KeyStore } from "./fields/Key" import { ObservableMap, computed, action, observable } from "mobx"; import { Document } from "./fields/Document" @@ -44,7 +44,7 @@ export class Server { @action - public static GetDocumentField(doc: Document, key: Key) { + public static GetDocumentField(doc: Document, key: Key): FieldValue { var fieldid = doc._proxies.get(key); if (!this.ClientFieldsCached.has(fieldid)) { this.ClientFieldsCached.set(fieldid, FieldWaiting); @@ -72,8 +72,8 @@ export class Server { public static SEND_DOCUMENT_FIELD_REQUEST(doc: Document, key: Key, fieldid: FIELD_ID) { //simulating a server call with a registered callback action - setTimeout(() => this.receivedDocumentField(doc, key, fieldid, this.FieldStore.get(fieldid)), - key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500 + setTimeout(() => this.receivedDocumentField(doc, key, fieldid, this.FieldStore.get(fieldid)), 50 + // key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500 ) } @@ -82,7 +82,7 @@ export class Server { this.receivedDocument(docid, this.DocumentStore.get(docid)!) } else { //simulating a server call with a registered callback action - setTimeout(() => this.receivedDocument(docid, this.DocumentStore.get(docid)!), 1500); + setTimeout(() => this.receivedDocument(docid, this.DocumentStore.get(docid)!), 50); } } diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 53138eda9..e482df622 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,4 +1,4 @@ -import { Field, Cast, Opt, FieldWaiting, FIELD_ID, DOC_ID } from "./Field" +import { Field, Cast, Opt, FieldWaiting, FIELD_ID, DOC_ID, FieldValue } from "./Field" import { Key, KeyStore } from "./Key" import { NumberField } from "./NumberField"; import { ObservableMap, computed, action, observable } from "mobx"; @@ -16,8 +16,8 @@ export class Document extends Field { return this.GetText(KeyStore.Title, ""); } - Get(key: Key, ignoreProto: boolean = false): Opt { - let field: Opt; + Get(key: Key, ignoreProto: boolean = false): FieldValue { + let field: FieldValue; if (ignoreProto) { if (this.fields.has(key)) { field = this.fields.get(key); @@ -25,7 +25,7 @@ export class Document extends Field { field = Server.GetDocumentField(this, key); } } else { - let doc: Opt = this; + let doc: FieldValue = this; while (doc && doc != FieldWaiting && field != FieldWaiting) { if (!doc.fields.has(key)) { if (doc._proxies.has(key)) { @@ -46,7 +46,7 @@ export class Document extends Field { return field; } - GetT(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): Opt { + GetT(key: Key, ctor: { new(...args: any[]): T }, ignoreProto: boolean = false): FieldValue { var getfield = this.Get(key, ignoreProto); if (getfield != FieldWaiting) { return Cast(getfield, ctor); @@ -119,13 +119,13 @@ export class Document extends Field { this.SetData(key, value, NumberField, replaceWrongType); } - GetPrototype(): Opt { + GetPrototype(): FieldValue { return this.GetT(KeyStore.Prototype, Document, true); } GetAllPrototypes(): Document[] { let protos: Document[] = []; - let doc: Opt = this; + let doc: FieldValue = this; while (doc && doc != FieldWaiting) { protos.push(doc); doc = doc.GetPrototype(); diff --git a/src/fields/DocumentReference.ts b/src/fields/DocumentReference.ts index 10dac9f92..983b162a3 100644 --- a/src/fields/DocumentReference.ts +++ b/src/fields/DocumentReference.ts @@ -1,4 +1,4 @@ -import { Field, Opt } from "./Field"; +import { Field, Opt, FieldValue } from "./Field"; import { Document } from "./Document"; import { Key } from "./Key"; @@ -15,12 +15,12 @@ export class DocumentReference extends Field { super(); } - Dereference(): Opt { + Dereference(): FieldValue { return this.document.Get(this.key); } - DereferenceToRoot(): Opt { - let field: Opt = this; + DereferenceToRoot(): FieldValue { + let field: FieldValue = this; while (field instanceof DocumentReference) { field = field.Dereference(); } @@ -37,5 +37,8 @@ export class DocumentReference extends Field { throw new Error("Method not implemented."); } + ToScriptString(): string { + return ""; + } } \ No newline at end of file diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 20d8bf5ed..2bfc9d1da 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -1,7 +1,7 @@ import { Utils } from "../Utils"; -export function Cast(field: Opt, ctor: { new(): T }): Opt { +export function Cast(field: FieldValue, ctor: { new(): T }): Opt { if (field) { if (ctor && field instanceof ctor) { return field; @@ -14,7 +14,8 @@ export let FieldWaiting: FIELD_WAITING = ""; export type FIELD_WAITING = ""; export type FIELD_ID = string | undefined; export type DOC_ID = FIELD_ID; -export type Opt = T | undefined | FIELD_WAITING; +export type Opt = T | undefined; +export type FieldValue = Opt | FIELD_WAITING; export abstract class Field { //FieldUpdated: TypedEvent> = new TypedEvent>(); @@ -28,18 +29,18 @@ export abstract class Field { this.id = id || Utils.GenerateGuid(); } - Dereference(): Opt { + Dereference(): FieldValue { return this; } - DereferenceToRoot(): Opt { + DereferenceToRoot(): FieldValue { return this; } - DereferenceT(ctor: { new(): T }): Opt { + DereferenceT(ctor: { new(): T }): FieldValue { return Cast(this.Dereference(), ctor); } - DereferenceToRootT(ctor: { new(): T }): Opt { + DereferenceToRootT(ctor: { new(): T }): FieldValue { return Cast(this.DereferenceToRoot(), ctor); } diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index bc2e7cdf4..63baf815b 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -10,6 +10,10 @@ export class ImageField extends BasicField { return this.Data.href; } + ToScriptString(): string { + return `new ImageField(${this.Data})`; + } + Copy(): Field { return new ImageField(this.Data); } diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 5cd43f55e..993102613 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -27,6 +27,9 @@ export class Key extends Field { return this; } + ToScriptString(): string { + return name; + } } diff --git a/src/fields/ListField.ts b/src/fields/ListField.ts index 8607ebe43..8843338c1 100644 --- a/src/fields/ListField.ts +++ b/src/fields/ListField.ts @@ -6,6 +6,10 @@ export class ListField extends BasicField { super(data.slice()); } + ToScriptString(): string { + return "new ListField([" + this.Data.map(field => field.ToScriptString()).join(", ") + "])"; + } + Copy(): Field { return new ListField(this.Data); } diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 24c7472d8..4a77c669c 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -5,6 +5,10 @@ export class RichTextField extends BasicField { super(data); } + ToScriptString(): string { + return `new RichTextField(${this.Data})`; + } + Copy() { return new RichTextField(this.Data); } diff --git a/src/fields/TextField.ts b/src/fields/TextField.ts index 95825d2ae..11d2ed7cd 100644 --- a/src/fields/TextField.ts +++ b/src/fields/TextField.ts @@ -5,6 +5,10 @@ export class TextField extends BasicField { super(data); } + ToScriptString(): string { + return `new TextField("${this.Data}")`; + } + Copy() { return new TextField(this.Data); } diff --git a/src/util/Scripting.ts b/src/util/Scripting.ts index 804c67bc5..e0d39bdfa 100644 --- a/src/util/Scripting.ts +++ b/src/util/Scripting.ts @@ -1,9 +1,9 @@ // import * as ts from "typescript" let ts = (window as any).ts; -import { Opt, Field, FieldWaiting } from "../fields/Field"; +import { Opt, Field } from "../fields/Field"; import { Document as DocumentImport } from "../fields/Document"; -import { NumberField as NumberFieldImport } from "../fields/NumberField"; -import { TextField as TextFieldImport } from "../fields/TextField"; +import { NumberField as NumberFieldImport, NumberField } from "../fields/NumberField"; +import { TextField as TextFieldImport, TextField } from "../fields/TextField"; import { RichTextField as RichTextFieldImport } from "../fields/RichTextField"; import { KeyStore as KeyStoreImport } from "../fields/Key"; @@ -14,7 +14,7 @@ export interface ExecutableScript { } function ExecScript(script: string, diagnostics: Opt): ExecutableScript { - const compiled = !(diagnostics && diagnostics != FieldWaiting && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); + const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); let func: () => Opt; if (compiled) { @@ -44,4 +44,13 @@ export function CompileScript(script: string): ExecutableScript { let result = (window as any).ts.transpileModule(script, {}) return ExecScript(result.outputText, result.diagnostics); +} + +export function ToField(data: any): Opt { + if (typeof data == "string") { + return new TextField(data); + } else if (typeof data == "number") { + return new NumberField(data); + } + return undefined; } \ No newline at end of file diff --git a/src/views/EditableView.tsx b/src/views/EditableView.tsx index 5f8d6ebcd..357c97af2 100644 --- a/src/views/EditableView.tsx +++ b/src/views/EditableView.tsx @@ -1,6 +1,6 @@ import React = require('react') import { observer } from 'mobx-react'; -import { observable } from 'mobx'; +import { observable, action } from 'mobx'; export interface EditableProps { GetValue(): string; @@ -13,21 +13,24 @@ export class EditableView extends React.Component { @observable editing: boolean = false; + @action onKeyDown = (e: React.KeyboardEvent) => { if (e.key == "Enter" && !e.ctrlKey) { this.props.SetValue(e.currentTarget.value); this.editing = false; + } else if (e.key == "Escape") { + this.editing = false; } } render() { if (this.editing) { - return + return this.editing = false)}> } else { return (
{this.props.contents} - +
) } diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx index e489e319a..adef03357 100644 --- a/src/views/collections/CollectionDockingView.tsx +++ b/src/views/collections/CollectionDockingView.tsx @@ -15,7 +15,6 @@ 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 { @@ -70,8 +69,6 @@ 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 @@ -255,8 +252,6 @@ 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 45d37ca4f..612f9acbe 100644 --- a/src/views/collections/CollectionFreeFormView.tsx +++ b/src/views/collections/CollectionFreeFormView.tsx @@ -33,23 +33,21 @@ 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 && doc.props.ContainingCollectionView != FieldWaiting) { + if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { 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!); - 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); - } + 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(); } @@ -88,7 +86,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerMove = (e: PointerEvent): void => { var me = this; - if (!e.cancelBubble && this.active && this.props.ContainingDocumentView != FieldWaiting) { + if (!e.cancelBubble && this.active) { e.preventDefault(); e.stopPropagation(); let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; @@ -105,8 +103,6 @@ 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; diff --git a/src/views/collections/CollectionSchemaView.tsx b/src/views/collections/CollectionSchemaView.tsx index c7a3d81a4..6f591a1bc 100644 --- a/src/views/collections/CollectionSchemaView.tsx +++ b/src/views/collections/CollectionSchemaView.tsx @@ -12,7 +12,7 @@ import { ScrollBox } from "../../util/ScrollBox"; import { CollectionViewBase } from "./CollectionViewBase"; import { DocumentView } from "../nodes/DocumentView"; import { EditableView } from "../EditableView"; -import { CompileScript } from "../../util/Scripting"; +import { CompileScript, ToField } from "../../util/Scripting"; import { Field } from "../../fields/Field"; @observer @@ -47,6 +47,12 @@ export class CollectionSchemaView extends CollectionViewBase { if (field instanceof Field) { props.doc.Set(props.fieldKey, field); return true; + } else { + let dataField = ToField(field); + if (dataField) { + props.doc.Set(props.fieldKey, dataField); + return true; + } } return false; }}> diff --git a/src/views/collections/CollectionViewBase.tsx b/src/views/collections/CollectionViewBase.tsx index 4fce02ef6..35d938d43 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, FieldWaiting } from "../../fields/Field"; +import { Opt } from "../../fields/Field"; import { Key, KeyStore } from "../../fields/Key"; import { ListField } from "../../fields/ListField"; import { SelectionManager } from "../../util/SelectionManager"; @@ -30,10 +30,9 @@ export class CollectionViewBase extends React.Component { 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 != FieldWaiting && this.props.ContainingDocumentView.props.ContainingCollectionView != FieldWaiting && ( - this.props.ContainingDocumentView.props.ContainingCollectionView == undefined || - this.props.ContainingDocumentView.props.ContainingCollectionView instanceof CollectionDockingView); + var topMost = this.props.ContainingDocumentView != undefined && ( + 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 25d67d96a..dc284e07c 100644 --- a/src/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/views/nodes/CollectionFreeFormDocumentView.tsx @@ -10,7 +10,6 @@ import { ContextMenu } from "../ContextMenu"; import "./NodeView.scss"; import React = require("react"); import { DocumentView, DocumentViewProps } from "./DocumentView"; -import { FieldWaiting } from "../../fields/Field"; @observer @@ -86,7 +85,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined || - (this.props.ContainingCollectionView != FieldWaiting && this.props.ContainingCollectionView!.active); + this.props.ContainingCollectionView!.active; } @computed diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx index 81353cd60..39be54a4d 100644 --- a/src/views/nodes/DocumentView.tsx +++ b/src/views/nodes/DocumentView.tsx @@ -49,8 +49,8 @@ export class DocumentView extends React.Component { // @computed public get ScalingToScreenSpace(): number { - if (this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView != FieldWaiting && - this.props.ContainingCollectionView.props.ContainingDocumentView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != FieldWaiting) { + if (this.props.ContainingCollectionView != undefined && + this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) { let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1)); return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss; } @@ -63,8 +63,8 @@ export class DocumentView extends React.Component { 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 != FieldWaiting && - this.props.ContainingCollectionView.props.ContainingDocumentView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != FieldWaiting ? + let { LocalX: parentX, LocalY: parentY } = this.props.ContainingCollectionView != undefined && + this.props.ContainingCollectionView.props.ContainingDocumentView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) : { LocalX: screenX, LocalY: screenY }; let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH; @@ -113,8 +113,8 @@ export class DocumentView extends React.Component { // 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 != FieldWaiting ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined; - if (containingDocView != undefined && containingDocView != FieldWaiting) { + let containingDocView = this.props.ContainingCollectionView != undefined ? this.props.ContainingCollectionView.props.ContainingDocumentView : undefined; + if (containingDocView != undefined) { 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; diff --git a/src/views/nodes/FieldView.tsx b/src/views/nodes/FieldView.tsx index 05a7b91b9..3a7652284 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, FieldWaiting } from "../../fields/Field"; +import { Field, Opt, FieldWaiting, FieldValue } from "../../fields/Field"; import { TextField } from "../../fields/TextField"; import { NumberField } from "../../fields/NumberField"; import { RichTextField } from "../../fields/RichTextField"; @@ -27,7 +27,7 @@ export interface FieldViewProps { export class FieldView extends React.Component { public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; } @computed - get field(): Opt { + get field(): FieldValue { const { doc, fieldKey } = this.props; return doc.Get(fieldKey); } diff --git a/src/views/nodes/FormattedTextBox.tsx b/src/views/nodes/FormattedTextBox.tsx index 3e3e22e46..6d0e117cc 100644 --- a/src/views/nodes/FormattedTextBox.tsx +++ b/src/views/nodes/FormattedTextBox.tsx @@ -6,7 +6,7 @@ import { keymap } from "prosemirror-keymap"; import { schema } from "prosemirror-schema-basic"; import { EditorState, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Opt, FieldWaiting } from "../../fields/Field"; +import { Opt, FieldWaiting, FieldValue } from "../../fields/Field"; import { SelectionManager } from "../../util/SelectionManager"; import "./FormattedTextBox.scss"; import React = require("react") @@ -48,7 +48,7 @@ export class FormattedTextBox extends React.Component { } dispatchTransaction = (tx: Transaction) => { - if (this._editorView && this._editorView != FieldWaiting) { + if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); const { doc, fieldKey } = this.props; @@ -85,17 +85,17 @@ export class FormattedTextBox extends React.Component { const field = this.props.doc.GetT(this.props.fieldKey, RichTextField); return field && field != FieldWaiting ? field.Data : undefined; }, (field) => { - if (field && this._editorView && this._editorView != FieldWaiting) { + if (field && this._editorView) { this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))); } }) } componentWillUnmount() { - if (this._editorView && this._editorView != FieldWaiting) { + if (this._editorView) { this._editorView.destroy(); } - if (this._reactionDisposer && this._reactionDisposer != FieldWaiting) { + if (this._reactionDisposer) { this._reactionDisposer(); } } -- cgit v1.2.3-70-g09d2 From c1581aaf93467cce8fca8a3da71181d79b3bfef8 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Fri, 8 Feb 2019 00:23:27 -0500 Subject: a couple small changes --- src/Main.tsx | 2 ++ src/fields/ImageField.ts | 4 ++-- src/util/Scripting.ts | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'src/fields') diff --git a/src/Main.tsx b/src/Main.tsx index a9ed1a365..fa6230393 100644 --- a/src/Main.tsx +++ b/src/Main.tsx @@ -13,6 +13,7 @@ import "./Main.scss"; import { ContextMenu } from './views/ContextMenu'; import { DocumentView } from './views/nodes/DocumentView'; import { CompileScript } from './util/Scripting'; +import { ImageField } from './fields/ImageField'; configure({ @@ -46,6 +47,7 @@ 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" }); + doc3.Set(KeyStore.Data, new ImageField); 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 })); diff --git a/src/fields/ImageField.ts b/src/fields/ImageField.ts index 63baf815b..d82260f54 100644 --- a/src/fields/ImageField.ts +++ b/src/fields/ImageField.ts @@ -3,7 +3,7 @@ import { Field } from "./Field"; export class ImageField extends BasicField { constructor(data: URL | undefined = undefined) { - super(data == undefined ? new URL("http://cs.brown.edu/~bcz/face.gif") : data); + super(data == undefined ? new URL("http://cs.brown.edu/~bcz/bob_fettucine.jpg") : data); } toString(): string { @@ -11,7 +11,7 @@ export class ImageField extends BasicField { } ToScriptString(): string { - return `new ImageField(${this.Data})`; + return `new ImageField("${this.Data}")`; } Copy(): Field { diff --git a/src/util/Scripting.ts b/src/util/Scripting.ts index e0d39bdfa..0e83ab4f0 100644 --- a/src/util/Scripting.ts +++ b/src/util/Scripting.ts @@ -3,6 +3,7 @@ let ts = (window as any).ts; import { Opt, Field } from "../fields/Field"; import { Document as DocumentImport } from "../fields/Document"; import { NumberField as NumberFieldImport, NumberField } from "../fields/NumberField"; +import { ImageField as ImageFieldImport } from "../fields/ImageField"; import { TextField as TextFieldImport, TextField } from "../fields/TextField"; import { RichTextField as RichTextFieldImport } from "../fields/RichTextField"; import { KeyStore as KeyStoreImport } from "../fields/Key"; @@ -23,6 +24,7 @@ function ExecScript(script: string, diagnostics: Opt): ExecutableScript { let Document = DocumentImport; let NumberField = NumberFieldImport; let TextField = TextFieldImport; + let ImageField = ImageFieldImport; let RichTextField = RichTextFieldImport; let window = undefined; let document = undefined; -- cgit v1.2.3-70-g09d2 From 11134bc5ce01d0a025d311a4f83e67ff6e63ce1c Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Sat, 9 Feb 2019 19:13:24 -0500 Subject: Moved client code to client folder --- src/DocumentDecorations.scss | 32 -- src/DocumentDecorations.tsx | 153 ---------- src/Main.scss | 31 -- src/Main.tsx | 92 ------ src/Server.tsx | 61 ---- src/SocketStub.tsx | 78 ----- src/client/Server.ts | 61 ++++ src/client/SocketStub.ts | 78 +++++ src/client/documents/Documents.ts | 161 ++++++++++ src/client/util/DragManager.ts | 131 ++++++++ src/client/util/Scripting.ts | 58 ++++ src/client/util/ScrollBox.tsx | 21 ++ src/client/util/SelectionManager.ts | 39 +++ src/client/util/TypedEvent.ts | 42 +++ src/client/views/ContextMenu.scss | 34 +++ src/client/views/ContextMenu.tsx | 68 +++++ src/client/views/ContextMenuItem.tsx | 17 ++ src/client/views/DocumentDecorations.scss | 32 ++ src/client/views/DocumentDecorations.tsx | 153 ++++++++++ src/client/views/EditableView.tsx | 39 +++ src/client/views/Main.scss | 31 ++ src/client/views/Main.tsx | 91 ++++++ .../views/collections/CollectionDockingView.scss | 336 +++++++++++++++++++++ .../views/collections/CollectionDockingView.tsx | 281 +++++++++++++++++ .../views/collections/CollectionFreeFormView.scss | 20 ++ .../views/collections/CollectionFreeFormView.tsx | 205 +++++++++++++ .../views/collections/CollectionSchemaView.scss | 108 +++++++ .../views/collections/CollectionSchemaView.tsx | 144 +++++++++ .../views/collections/CollectionViewBase.tsx | 57 ++++ .../views/nodes/CollectionFreeFormDocumentView.tsx | 223 ++++++++++++++ src/client/views/nodes/DocumentView.tsx | 153 ++++++++++ src/client/views/nodes/FieldTextBox.scss | 14 + src/client/views/nodes/FieldView.tsx | 56 ++++ src/client/views/nodes/FormattedTextBox.scss | 14 + src/client/views/nodes/FormattedTextBox.tsx | 127 ++++++++ src/client/views/nodes/ImageBox.scss | 11 + src/client/views/nodes/ImageBox.tsx | 92 ++++++ src/client/views/nodes/NodeView.scss | 23 ++ src/documents/Documents.ts | 161 ---------- src/fields/Document.ts | 2 +- src/server/index.js | 0 src/util/DragManager.ts | 137 --------- src/util/Scripting.ts | 58 ---- src/util/ScrollBox.tsx | 21 -- src/util/SelectionManager.ts | 39 --- src/util/TypedEvent.ts | 42 --- src/views/ContextMenu.scss | 34 --- src/views/ContextMenu.tsx | 68 ----- src/views/ContextMenuItem.tsx | 17 -- src/views/EditableView.tsx | 39 --- src/views/collections/CollectionDockingView.scss | 336 --------------------- src/views/collections/CollectionDockingView.tsx | 282 ----------------- src/views/collections/CollectionFreeFormView.scss | 20 -- src/views/collections/CollectionFreeFormView.tsx | 206 ------------- src/views/collections/CollectionSchemaView.scss | 108 ------- src/views/collections/CollectionSchemaView.tsx | 144 --------- src/views/collections/CollectionViewBase.tsx | 57 ---- src/views/nodes/CollectionFreeFormDocumentView.tsx | 223 -------------- src/views/nodes/DocumentView.tsx | 153 ---------- src/views/nodes/FieldTextBox.scss | 14 - src/views/nodes/FieldView.tsx | 56 ---- src/views/nodes/FormattedTextBox.scss | 14 - src/views/nodes/FormattedTextBox.tsx | 127 -------- src/views/nodes/ImageBox.scss | 11 - src/views/nodes/ImageBox.tsx | 92 ------ src/views/nodes/NodeView.scss | 23 -- webpack.config.js | 2 +- 67 files changed, 2922 insertions(+), 2931 deletions(-) delete mode 100644 src/DocumentDecorations.scss delete mode 100644 src/DocumentDecorations.tsx delete mode 100644 src/Main.scss delete mode 100644 src/Main.tsx delete mode 100644 src/Server.tsx delete mode 100644 src/SocketStub.tsx create mode 100644 src/client/Server.ts create mode 100644 src/client/SocketStub.ts create mode 100644 src/client/documents/Documents.ts create mode 100644 src/client/util/DragManager.ts create mode 100644 src/client/util/Scripting.ts create mode 100644 src/client/util/ScrollBox.tsx create mode 100644 src/client/util/SelectionManager.ts create mode 100644 src/client/util/TypedEvent.ts create mode 100644 src/client/views/ContextMenu.scss create mode 100644 src/client/views/ContextMenu.tsx create mode 100644 src/client/views/ContextMenuItem.tsx create mode 100644 src/client/views/DocumentDecorations.scss create mode 100644 src/client/views/DocumentDecorations.tsx create mode 100644 src/client/views/EditableView.tsx create mode 100644 src/client/views/Main.scss create mode 100644 src/client/views/Main.tsx create mode 100644 src/client/views/collections/CollectionDockingView.scss create mode 100644 src/client/views/collections/CollectionDockingView.tsx create mode 100644 src/client/views/collections/CollectionFreeFormView.scss create mode 100644 src/client/views/collections/CollectionFreeFormView.tsx create mode 100644 src/client/views/collections/CollectionSchemaView.scss create mode 100644 src/client/views/collections/CollectionSchemaView.tsx create mode 100644 src/client/views/collections/CollectionViewBase.tsx create mode 100644 src/client/views/nodes/CollectionFreeFormDocumentView.tsx create mode 100644 src/client/views/nodes/DocumentView.tsx create mode 100644 src/client/views/nodes/FieldTextBox.scss create mode 100644 src/client/views/nodes/FieldView.tsx create mode 100644 src/client/views/nodes/FormattedTextBox.scss create mode 100644 src/client/views/nodes/FormattedTextBox.tsx create mode 100644 src/client/views/nodes/ImageBox.scss create mode 100644 src/client/views/nodes/ImageBox.tsx create mode 100644 src/client/views/nodes/NodeView.scss delete mode 100644 src/documents/Documents.ts create mode 100644 src/server/index.js delete mode 100644 src/util/DragManager.ts delete mode 100644 src/util/Scripting.ts delete mode 100644 src/util/ScrollBox.tsx delete mode 100644 src/util/SelectionManager.ts delete mode 100644 src/util/TypedEvent.ts delete mode 100644 src/views/ContextMenu.scss delete mode 100644 src/views/ContextMenu.tsx delete mode 100644 src/views/ContextMenuItem.tsx delete mode 100644 src/views/EditableView.tsx delete mode 100644 src/views/collections/CollectionDockingView.scss delete mode 100644 src/views/collections/CollectionDockingView.tsx delete mode 100644 src/views/collections/CollectionFreeFormView.scss delete mode 100644 src/views/collections/CollectionFreeFormView.tsx delete mode 100644 src/views/collections/CollectionSchemaView.scss delete mode 100644 src/views/collections/CollectionSchemaView.tsx delete mode 100644 src/views/collections/CollectionViewBase.tsx delete mode 100644 src/views/nodes/CollectionFreeFormDocumentView.tsx delete mode 100644 src/views/nodes/DocumentView.tsx delete mode 100644 src/views/nodes/FieldTextBox.scss delete mode 100644 src/views/nodes/FieldView.tsx delete mode 100644 src/views/nodes/FormattedTextBox.scss delete mode 100644 src/views/nodes/FormattedTextBox.tsx delete mode 100644 src/views/nodes/ImageBox.scss delete mode 100644 src/views/nodes/ImageBox.tsx delete mode 100644 src/views/nodes/NodeView.scss (limited to 'src/fields') diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss deleted file mode 100644 index e8b93a18b..000000000 --- a/src/DocumentDecorations.scss +++ /dev/null @@ -1,32 +0,0 @@ -#documentDecorations-container { - position: absolute; - display: grid; - z-index: 1000; - grid-template-rows: 20px 1fr 20px; - grid-template-columns: 20px 1fr 20px; - pointer-events: none; - #documentDecorations-centerCont { - background: none; - } - .documentDecorations-resizer { - pointer-events: auto; - background: lightblue; - opacity: 0.4; - } - #documentDecorations-topLeftResizer, - #documentDecorations-bottomRightResizer { - cursor: nwse-resize; - } - #documentDecorations-topRightResizer, - #documentDecorations-bottomLeftResizer { - cursor: nesw-resize; - } - #documentDecorations-topResizer, - #documentDecorations-bottomResizer { - cursor: ns-resize; - } - #documentDecorations-leftResizer, - #documentDecorations-rightResizer { - cursor: ew-resize; - } -} \ No newline at end of file diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx deleted file mode 100644 index 1cf875ea5..000000000 --- a/src/DocumentDecorations.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { observable, computed } from "mobx"; -import React = require("react"); -import { SelectionManager } from "./util/SelectionManager"; -import { observer } from "mobx-react"; -import './DocumentDecorations.scss' -import { CollectionFreeFormView } from "./views/collections/CollectionFreeFormView"; - -@observer -export class DocumentDecorations extends React.Component { - static Instance: DocumentDecorations - private _resizer = "" - private _isPointerDown = false; - @observable private _opacity = 1; - - constructor(props: Readonly<{}>) { - super(props) - - DocumentDecorations.Instance = this - } - - @computed - get Bounds(): { x: number, y: number, b: number, r: number } { - return SelectionManager.SelectedDocuments().reduce((bounds, element) => { - if (element.props.ContainingCollectionView != undefined && - !(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) { - return bounds; - } - var spt = element.TransformToScreenPoint(0, 0); - var bpt = element.TransformToScreenPoint(element.width, element.height); - return { - x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y), - r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b) - } - }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); - } - - @computed - get opacity(): number { - return this._opacity - } - - set opacity(o: number) { - this._opacity = Math.min(Math.max(0, o), 1) - } - - onPointerDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - if (e.button === 0) { - this._isPointerDown = true; - this._resizer = e.currentTarget.id; - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } - - onPointerMove = (e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - if (!this._isPointerDown) { - return; - } - - let dX = 0, dY = 0, dW = 0, dH = 0; - - switch (this._resizer) { - case "": - break; - case "documentDecorations-topLeftResizer": - dX = -1 - dY = -1 - dW = -(e.movementX) - dH = -(e.movementY) - break; - case "documentDecorations-topRightResizer": - dW = e.movementX - dY = -1 - dH = -(e.movementY) - break; - case "documentDecorations-topResizer": - dY = -1 - dH = -(e.movementY) - break; - case "documentDecorations-bottomLeftResizer": - dX = -1 - dW = -(e.movementX) - dH = e.movementY - break; - case "documentDecorations-bottomRightResizer": - dW = e.movementX - dH = e.movementY - break; - case "documentDecorations-bottomResizer": - dH = e.movementY - break; - case "documentDecorations-leftResizer": - dX = -1 - dW = -(e.movementX) - break; - case "documentDecorations-rightResizer": - dW = e.movementX - break; - } - - SelectionManager.SelectedDocuments().forEach(element => { - const rect = element.screenRect; - if (rect.width !== 0) { - let scale = element.width / rect.width; - let actualdW = Math.max(element.width + (dW * scale), 20); - let actualdH = Math.max(element.height + (dH * scale), 20); - element.x += dX * (actualdW - element.width); - element.y += dY * (actualdH - element.height); - element.width = actualdW; - element.height = actualdH; - } - }) - } - - onPointerUp = (e: PointerEvent): void => { - e.stopPropagation(); - if (e.button === 0) { - e.preventDefault(); - this._isPointerDown = false; - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } - } - - render() { - var bounds = this.Bounds; - return ( -
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
-
e.preventDefault()}>
- -
- ) - } -} \ No newline at end of file diff --git a/src/Main.scss b/src/Main.scss deleted file mode 100644 index e73f62904..000000000 --- a/src/Main.scss +++ /dev/null @@ -1,31 +0,0 @@ -html, -body { - width: 100%; - height: 100%; - overflow: hidden; - font-family: 'Hind Siliguri', sans-serif; - margin: 0; -} - -h1 { - font-size: 50px; - position: fixed; - top: 30px; - left: 50%; - transform: translateX(-50%); - color: black; - text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; - z-index: 9999; - font-family: 'Fjalla One', sans-serif; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -p { - margin: 0px; - padding: 0px; -} \ No newline at end of file diff --git a/src/Main.tsx b/src/Main.tsx deleted file mode 100644 index fa6230393..000000000 --- a/src/Main.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { action, configure } from 'mobx'; -import "normalize.css"; -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { DocumentDecorations } from './DocumentDecorations'; -import { Documents } from './documents/Documents'; -import { Document } from './fields/Document'; -import { KeyStore, KeyStore as KS } from './fields/Key'; -import { ListField } from './fields/ListField'; -import { NumberField } from './fields/NumberField'; -import { TextField } from './fields/TextField'; -import "./Main.scss"; -import { ContextMenu } from './views/ContextMenu'; -import { DocumentView } from './views/nodes/DocumentView'; -import { CompileScript } from './util/Scripting'; -import { ImageField } from './fields/ImageField'; - - -configure({ - enforceActions: "observed" -}); - -const mainNodeCollection = new Array(); -let mainContainer = Documents.DockDocument(mainNodeCollection, { - x: 0, y: 0, title: "main container" -}) - -window.addEventListener("drop", function (e) { - e.preventDefault(); -}, false) -window.addEventListener("dragover", function (e) { - e.preventDefault(); -}, false) -document.addEventListener("pointerdown", action(function (e: PointerEvent) { - if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { - ContextMenu.Instance.clearItems() - } -}), true) - - -//runInAction(() => -{ - let doc1 = Documents.TextDocument({ title: "hello" }); - let doc2 = doc1.MakeDelegate(); - doc2.Set(KS.X, new NumberField(150)); - doc2.Set(KS.Y, new NumberField(20)); - 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" - }); - doc3.Set(KeyStore.Data, new ImageField); - 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]; - 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 docset2 = new Array(doc4);//, doc1, doc3); - let doc6 = Documents.CollectionDocument(docset2, { - x: 350, y: 100, width: 600, height: 600, title: "docking collection" - }); - let mainNodes = null;// mainContainer.GetFieldT(KeyStore.Data, ListField); - if (!mainNodes) { - mainNodes = new ListField(); - } - // mainNodes.Data.push(doc6); - // mainNodes.Data.push(doc2); - mainNodes.Data.push(doc4); - mainNodes.Data.push(doc3); - // mainNodes.Data.push(doc5); - // mainNodes.Data.push(doc1); - //mainNodes.Data.push(doc2); - //mainNodes.Data.push(doc6); - mainContainer.Set(KeyStore.Data, mainNodes); -} -//} -//); - -ReactDOM.render(( -
- - - -
), - document.getElementById('root')); \ No newline at end of file diff --git a/src/Server.tsx b/src/Server.tsx deleted file mode 100644 index 645420771..000000000 --- a/src/Server.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "./fields/Field" -import { Key, KeyStore } from "./fields/Key" -import { ObservableMap, action } from "mobx"; -import { Document } from "./fields/Document" -import { SocketStub } from "./SocketStub"; - -export class Server { - private static ClientFieldsCached: ObservableMap = new ObservableMap(); - - // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). - // Call this is from within a reaction and test whether the return value is FieldWaiting. - // 'hackTimeout' is here temporarily for simplicity when debugging things. - public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { - if (!this.ClientFieldsCached.get(fieldid)) { - this.ClientFieldsCached.set(fieldid, FieldWaiting); - //simulating a server call with a registered callback action - SocketStub.SEND_FIELD_REQUEST(fieldid, - action((field: Field) => callback(Server.cacheField(field))), - hackTimeout); - } else if (this.ClientFieldsCached.get(fieldid) != FieldWaiting) { - callback(this.ClientFieldsCached.get(fieldid) as Field); - } - return this.ClientFieldsCached.get(fieldid); - } - - static times = 0; // hack for testing - public static GetDocumentField(doc: Document, key: Key) { - var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; - - return this.GetField(doc._proxies.get(key), - action((fieldfromserver: Field) => { - doc._proxies.delete(key); - doc.fields.set(key, fieldfromserver); - }) - , hackTimeout); - } - - public static AddDocument(document: Document) { - SocketStub.SEND_ADD_DOCUMENT(document); - } - public static AddDocumentField(doc: Document, key: Key, value: Field) { - SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value); - } - public static DeleteDocumentField(doc: Document, key: Key) { - SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key); - } - public static SetFieldValue(field: Field, value: any) { - SocketStub.SEND_SET_FIELD(field, value); - } - - @action - private static cacheField(clientField: Field) { - var cached = this.ClientFieldsCached.get(clientField.Id); - if (!cached || cached == FieldWaiting) { - this.ClientFieldsCached.set(clientField.Id, clientField); - } else { - // probably should overwrite the values within any field that was already here... - } - return this.ClientFieldsCached.get(clientField.Id) as Field; - } -} diff --git a/src/SocketStub.tsx b/src/SocketStub.tsx deleted file mode 100644 index f9d48c067..000000000 --- a/src/SocketStub.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Field, FIELD_ID } from "./fields/Field" -import { Key, KeyStore } from "./fields/Key" -import { ObservableMap, action } from "mobx"; -import { Document } from "./fields/Document" - -export class SocketStub { - - static FieldStore: ObservableMap = new ObservableMap(); - public static SEND_ADD_DOCUMENT(document: Document) { - - // Send a serialized version of the document to the server - // ...SOCKET(ADD_DOCUMENT, serialied document) - - // server stores each document field in its repository of stored fields - document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field)); - - // server stores stripped down document (w/ only field id proxies) in the field store - this.FieldStore.set(document.Id, new Document(document.Id)); - document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id)); - } - - public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) { - - if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that. - callback(this.FieldStore.get(fieldid) as Field); - else { // actual logic here... - - // Send a request for fieldid to the server - // ...SOCKET(RETRIEVE_FIELD, fieldid) - - // server responds (simulated with a timeout) and the callback is invoked - setTimeout(() => - - // when the field data comes back, call the callback() function - callback(this.FieldStore.get(fieldid) as Field), - - - timeout); - } - } - - public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) { - - // Send a serialized version of the field to the server along with the - // associated info of the document id and key where it is used. - - // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field) - - // server updates its document to hold a proxy mapping from key => fieldId - var document = this.FieldStore.get(doc.Id) as Document; - if (document) - document._proxies.set(key, value.Id); - - // server adds the field to its repository of fields - this.FieldStore.set(value.Id, value); - } - - public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) { - // Send a request to delete the field stored under the specified key from the document - - // ...SOCKET(DELETE_DOCUMENT_FIELD, document id, key id) - - // Server removes the field id from the document's list of field proxies - var document = this.FieldStore.get(doc.Id) as Document; - if (document) - document._proxies.delete(key); - } - - public static SEND_SET_FIELD(field: Field, value: any) { - // Send a request to set the value of a field - - // ...SOCKET(SET_FIELD, field id, serialized field value) - - // Server updates the value of the field in its fieldstore - if (this.FieldStore.get(field.Id)) - this.FieldStore.get(field.Id)!.TrySetValue(value); - } -} diff --git a/src/client/Server.ts b/src/client/Server.ts new file mode 100644 index 000000000..85e55a84e --- /dev/null +++ b/src/client/Server.ts @@ -0,0 +1,61 @@ +import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "../fields/Field" +import { Key, KeyStore } from "../fields/Key" +import { ObservableMap, action } from "mobx"; +import { Document } from "../fields/Document" +import { SocketStub } from "./SocketStub"; + +export class Server { + private static ClientFieldsCached: ObservableMap = new ObservableMap(); + + // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). + // Call this is from within a reaction and test whether the return value is FieldWaiting. + // 'hackTimeout' is here temporarily for simplicity when debugging things. + public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { + if (!this.ClientFieldsCached.get(fieldid)) { + this.ClientFieldsCached.set(fieldid, FieldWaiting); + //simulating a server call with a registered callback action + SocketStub.SEND_FIELD_REQUEST(fieldid, + action((field: Field) => callback(Server.cacheField(field))), + hackTimeout); + } else if (this.ClientFieldsCached.get(fieldid) != FieldWaiting) { + callback(this.ClientFieldsCached.get(fieldid) as Field); + } + return this.ClientFieldsCached.get(fieldid); + } + + static times = 0; // hack for testing + public static GetDocumentField(doc: Document, key: Key) { + var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; + + return this.GetField(doc._proxies.get(key), + action((fieldfromserver: Field) => { + doc._proxies.delete(key); + doc.fields.set(key, fieldfromserver); + }) + , hackTimeout); + } + + public static AddDocument(document: Document) { + SocketStub.SEND_ADD_DOCUMENT(document); + } + public static AddDocumentField(doc: Document, key: Key, value: Field) { + SocketStub.SEND_ADD_DOCUMENT_FIELD(doc, key, value); + } + public static DeleteDocumentField(doc: Document, key: Key) { + SocketStub.SEND_DELETE_DOCUMENT_FIELD(doc, key); + } + public static SetFieldValue(field: Field, value: any) { + SocketStub.SEND_SET_FIELD(field, value); + } + + @action + private static cacheField(clientField: Field) { + var cached = this.ClientFieldsCached.get(clientField.Id); + if (!cached || cached == FieldWaiting) { + this.ClientFieldsCached.set(clientField.Id, clientField); + } else { + // probably should overwrite the values within any field that was already here... + } + return this.ClientFieldsCached.get(clientField.Id) as Field; + } +} diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts new file mode 100644 index 000000000..58dedbf82 --- /dev/null +++ b/src/client/SocketStub.ts @@ -0,0 +1,78 @@ +import { Field, FIELD_ID } from "../fields/Field" +import { Key, KeyStore } from "../fields/Key" +import { ObservableMap, action } from "mobx"; +import { Document } from "../fields/Document" + +export class SocketStub { + + static FieldStore: ObservableMap = new ObservableMap(); + public static SEND_ADD_DOCUMENT(document: Document) { + + // Send a serialized version of the document to the server + // ...SOCKET(ADD_DOCUMENT, serialied document) + + // server stores each document field in its repository of stored fields + document.fields.forEach((f, key) => this.FieldStore.set((f as Field).Id, f as Field)); + + // server stores stripped down document (w/ only field id proxies) in the field store + this.FieldStore.set(document.Id, new Document(document.Id)); + document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id)); + } + + public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) { + + if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that. + callback(this.FieldStore.get(fieldid) as Field); + else { // actual logic here... + + // Send a request for fieldid to the server + // ...SOCKET(RETRIEVE_FIELD, fieldid) + + // server responds (simulated with a timeout) and the callback is invoked + setTimeout(() => + + // when the field data comes back, call the callback() function + callback(this.FieldStore.get(fieldid) as Field), + + + timeout); + } + } + + public static SEND_ADD_DOCUMENT_FIELD(doc: Document, key: Key, value: Field) { + + // Send a serialized version of the field to the server along with the + // associated info of the document id and key where it is used. + + // ...SOCKET(ADD_DOCUMENT_FIELD, document id, key id, serialized field) + + // server updates its document to hold a proxy mapping from key => fieldId + var document = this.FieldStore.get(doc.Id) as Document; + if (document) + document._proxies.set(key, value.Id); + + // server adds the field to its repository of fields + this.FieldStore.set(value.Id, value); + } + + public static SEND_DELETE_DOCUMENT_FIELD(doc: Document, key: Key) { + // Send a request to delete the field stored under the specified key from the document + + // ...SOCKET(DELETE_DOCUMENT_FIELD, document id, key id) + + // Server removes the field id from the document's list of field proxies + var document = this.FieldStore.get(doc.Id) as Document; + if (document) + document._proxies.delete(key); + } + + public static SEND_SET_FIELD(field: Field, value: any) { + // Send a request to set the value of a field + + // ...SOCKET(SET_FIELD, field id, serialized field value) + + // Server updates the value of the field in its fieldstore + if (this.FieldStore.get(field.Id)) + this.FieldStore.get(field.Id)!.TrySetValue(value); + } +} diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts new file mode 100644 index 000000000..6925234fe --- /dev/null +++ b/src/client/documents/Documents.ts @@ -0,0 +1,161 @@ +import { Document } from "../../fields/Document"; +import { Server } from "../Server"; +import { KeyStore } from "../../fields/Key"; +import { TextField } from "../../fields/TextField"; +import { NumberField } from "../../fields/NumberField"; +import { ListField } from "../../fields/ListField"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; +import { CollectionSchemaView } from "../views/collections/CollectionSchemaView"; +import { ImageField } from "../../fields/ImageField"; +import { ImageBox } from "../views/nodes/ImageBox"; +import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView"; +import { FIELD_ID } from "../../fields/Field"; + +interface DocumentOptions { + x?: number; + y?: number; + width?: number; + height?: number; + title?: string; +} + +export namespace Documents { + function setupOptions(doc: Document, options: DocumentOptions): void { + if (options.x) { + doc.SetData(KeyStore.X, options.x, NumberField); + } + if (options.y) { + doc.SetData(KeyStore.Y, options.y, NumberField); + } + if (options.width) { + doc.SetData(KeyStore.Width, options.width, NumberField); + } + if (options.height) { + doc.SetData(KeyStore.Height, options.height, NumberField); + } + if (options.title) { + doc.SetData(KeyStore.Title, options.title, TextField); + } + doc.SetData(KeyStore.Scale, 1, NumberField); + doc.SetData(KeyStore.PanX, 0, NumberField); + doc.SetData(KeyStore.PanY, 0, NumberField); + } + + let textProto: Document; + function GetTextPrototype(): Document { + if (!textProto) { + textProto = new Document(); + textProto.Set(KeyStore.X, new NumberField(0)); + textProto.Set(KeyStore.Y, new NumberField(0)); + textProto.Set(KeyStore.Width, new NumberField(300)); + textProto.Set(KeyStore.Height, new NumberField(150)); + textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString())); + textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + } + return textProto; + } + + export function TextDocument(options: DocumentOptions = {}): Document { + let doc = GetTextPrototype().MakeDelegate(); + setupOptions(doc, options); + // doc.SetField(KeyStore.Data, new RichTextField()); + return doc; + } + + let schemaProto: Document; + function GetSchemaPrototype(): Document { + if (!schemaProto) { + schemaProto = new Document(); + schemaProto.Set(KeyStore.X, new NumberField(0)); + schemaProto.Set(KeyStore.Y, new NumberField(0)); + schemaProto.Set(KeyStore.Width, new NumberField(300)); + schemaProto.Set(KeyStore.Height, new NumberField(150)); + schemaProto.Set(KeyStore.Layout, new TextField(CollectionSchemaView.LayoutString())); + schemaProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + } + return schemaProto; + } + + export function SchemaDocument(documents: Array, options: DocumentOptions = {}): Document { + let doc = GetSchemaPrototype().MakeDelegate(); + setupOptions(doc, options); + doc.Set(KeyStore.Data, new ListField(documents)); + return doc; + } + + + let dockProto: Document; + function GetDockPrototype(): Document { + if (!dockProto) { + dockProto = new Document(); + dockProto.Set(KeyStore.X, new NumberField(0)); + dockProto.Set(KeyStore.Y, new NumberField(0)); + dockProto.Set(KeyStore.Width, new NumberField(300)); + dockProto.Set(KeyStore.Height, new NumberField(150)); + dockProto.Set(KeyStore.Layout, new TextField(CollectionDockingView.LayoutString())); + dockProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + } + return dockProto; + } + + export function DockDocument(documents: Array, options: DocumentOptions = {}): Document { + let doc = GetDockPrototype().MakeDelegate(); + setupOptions(doc, options); + doc.Set(KeyStore.Data, new ListField(documents)); + return doc; + } + + + let imageProtoId: FIELD_ID; + function GetImagePrototype(): 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)); + imageProto.Set(KeyStore.Height, new NumberField(300)); + imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString())); + // imageProto.SetField(KeyStore.Layout, new TextField('
')); + imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + Server.AddDocument(imageProto); + return imageProto; + } + return Server.GetField(imageProtoId) as Document; + } + + export function ImageDocument(url: string, options: DocumentOptions = {}): Document { + let doc = GetImagePrototype().MakeDelegate(); + setupOptions(doc, options); + doc.Set(KeyStore.Data, new ImageField(new URL(url))); + Server.AddDocument(doc); + var sdoc = Server.GetField(doc.Id) as Document; + return sdoc; + } + + let collectionProto: Document; + function GetCollectionPrototype(): Document { + if (!collectionProto) { + collectionProto = new Document(); + collectionProto.Set(KeyStore.X, new NumberField(0)); + collectionProto.Set(KeyStore.Y, new NumberField(0)); + collectionProto.Set(KeyStore.Scale, new NumberField(1)); + collectionProto.Set(KeyStore.PanX, new NumberField(0)); + collectionProto.Set(KeyStore.PanY, new NumberField(0)); + collectionProto.Set(KeyStore.Width, new NumberField(300)); + collectionProto.Set(KeyStore.Height, new NumberField(300)); + collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString())); + collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + } + return collectionProto; + } + + export function CollectionDocument(documents: Array, options: DocumentOptions = {}): Document { + let doc = GetCollectionPrototype().MakeDelegate(); + setupOptions(doc, options); + doc.Set(KeyStore.Data, new ListField(documents)); + return doc; + } +} \ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts new file mode 100644 index 000000000..f4dcce7c8 --- /dev/null +++ b/src/client/util/DragManager.ts @@ -0,0 +1,131 @@ + +export namespace DragManager { + export function Root() { + const root = document.getElementById("root"); + if (!root) { + throw new Error("No root element found"); + } + return root; + } + + let dragDiv: HTMLDivElement; + + export enum DragButtons { + Left = 1, Right = 2, Both = Left | Right + } + + interface DragOptions { + handlers: DragHandlers; + + hideSource: boolean | (() => boolean); + } + + export interface DragDropDisposer { + (): void; + } + + export class DragCompleteEvent { + } + + export interface DragHandlers { + dragComplete: (e: DragCompleteEvent) => void; + } + + export interface DropOptions { + handlers: DropHandlers; + } + + export class DropEvent { + constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { } + } + + export interface DropHandlers { + drop: (e: Event, de: DropEvent) => void; + } + + export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer { + if ("canDrop" in element.dataset) { + throw new Error("Element is already droppable, can't make it droppable again"); + } + element.dataset["canDrop"] = "true"; + const handler = (e: Event) => { + const ce = e as CustomEvent; + options.handlers.drop(e, ce.detail); + }; + element.addEventListener("dashOnDrop", handler); + return () => { + element.removeEventListener("dashOnDrop", handler); + delete element.dataset["canDrop"] + }; + } + + + let _lastPointerX: number = 0; + let _lastPointerY: number = 0; + export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) { + if (!dragDiv) { + dragDiv = document.createElement("div"); + DragManager.Root().appendChild(dragDiv); + } + const w = ele.offsetWidth, h = ele.offsetHeight; + const rect = ele.getBoundingClientRect(); + const scaleX = rect.width / w, scaleY = rect.height / h; + let x = rect.left, y = rect.top; + // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.zIndex = "1000"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragDiv.appendChild(dragElement); + _lastPointerX = dragData["xOffset"] + rect.left; + _lastPointerY = dragData["yOffset"] + rect.top; + + let hideSource = false; + if (typeof options.hideSource === "boolean") { + hideSource = options.hideSource; + } else { + hideSource = options.hideSource(); + } + const wasHidden = ele.hidden; + if (hideSource) { + ele.hidden = true; + } + + const moveHandler = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + x += e.clientX - _lastPointerX; _lastPointerX = e.clientX; + y += e.clientY - _lastPointerY; _lastPointerY = e.clientY; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + }; + const upHandler = (e: PointerEvent) => { + document.removeEventListener("pointermove", moveHandler, true); + document.removeEventListener("pointerup", upHandler); + FinishDrag(dragElement, e, options, dragData); + if (hideSource && !wasHidden) { + ele.hidden = false; + } + }; + document.addEventListener("pointermove", moveHandler, true); + document.addEventListener("pointerup", upHandler); + } + + function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) { + dragDiv.removeChild(dragEle); + const target = document.elementFromPoint(e.x, e.y); + if (!target) { + return; + } + target.dispatchEvent(new CustomEvent("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + })); + options.handlers.dragComplete({}); + } +} \ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts new file mode 100644 index 000000000..6bc5fa412 --- /dev/null +++ b/src/client/util/Scripting.ts @@ -0,0 +1,58 @@ +// import * as ts from "typescript" +let ts = (window as any).ts; +import { Opt, Field } from "../../fields/Field"; +import { Document as DocumentImport } from "../../fields/Document"; +import { NumberField as NumberFieldImport, NumberField } from "../../fields/NumberField"; +import { ImageField as ImageFieldImport } from "../../fields/ImageField"; +import { TextField as TextFieldImport, TextField } from "../../fields/TextField"; +import { RichTextField as RichTextFieldImport } from "../../fields/RichTextField"; +import { KeyStore as KeyStoreImport } from "../../fields/Key"; + +export interface ExecutableScript { + (): any; + + compiled: boolean; +} + +function ExecScript(script: string, diagnostics: Opt): ExecutableScript { + const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); + + let func: () => Opt; + if (compiled) { + func = function (): Opt { + let KeyStore = KeyStoreImport; + let Document = DocumentImport; + let NumberField = NumberFieldImport; + let TextField = TextFieldImport; + let ImageField = ImageFieldImport; + let RichTextField = RichTextFieldImport; + let window = undefined; + let document = undefined; + let retVal = eval(script); + + return retVal; + }; + } else { + func = () => undefined; + } + + return Object.assign(func, + { + compiled + }); +} + +export function CompileScript(script: string): ExecutableScript { + let result = (window as any).ts.transpileModule(script, {}) + + return ExecScript(result.outputText, result.diagnostics); +} + +export function ToField(data: any): Opt { + if (typeof data == "string") { + return new TextField(data); + } else if (typeof data == "number") { + return new NumberField(data); + } + return undefined; +} \ No newline at end of file diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx new file mode 100644 index 000000000..b6b088170 --- /dev/null +++ b/src/client/util/ScrollBox.tsx @@ -0,0 +1,21 @@ +import React = require("react") + +export class ScrollBox extends React.Component { + onWheel = (e: React.WheelEvent) => { + if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom + e.stopPropagation(); + } + } + + render() { + return ( +
+ {this.props.children} +
+ ) + } +} \ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts new file mode 100644 index 000000000..0759ae110 --- /dev/null +++ b/src/client/util/SelectionManager.ts @@ -0,0 +1,39 @@ +import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; +import { observable, action } from "mobx"; + +export namespace SelectionManager { + class Manager { + @observable + SelectedDocuments: Array = []; + + @action + SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { + // if doc is not in SelectedDocuments, add it + if (!ctrlPressed) { + manager.SelectedDocuments = []; + } + + if (manager.SelectedDocuments.indexOf(doc) === -1) { + manager.SelectedDocuments.push(doc) + } + } + } + + const manager = new Manager; + + export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { + manager.SelectDoc(doc, ctrlPressed) + } + + export function IsSelected(doc: CollectionFreeFormDocumentView): boolean { + return manager.SelectedDocuments.indexOf(doc) !== -1; + } + + export function DeselectAll(): void { + manager.SelectedDocuments = [] + } + + export function SelectedDocuments(): Array { + return manager.SelectedDocuments; + } +} \ No newline at end of file diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts new file mode 100644 index 000000000..0714a7f5c --- /dev/null +++ b/src/client/util/TypedEvent.ts @@ -0,0 +1,42 @@ +export interface Listener { + (event: T): any; +} + +export interface Disposable { + dispose(): void; +} + +/** passes through events as they happen. You will not get events from before you start listening */ +export class TypedEvent { + private listeners: Listener[] = []; + private listenersOncer: Listener[] = []; + + on = (listener: Listener): Disposable => { + this.listeners.push(listener); + return { + dispose: () => this.off(listener) + }; + } + + once = (listener: Listener): void => { + this.listenersOncer.push(listener); + } + + off = (listener: Listener) => { + var callbackIndex = this.listeners.indexOf(listener); + if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); + } + + emit = (event: T) => { + /** Update any general listeners */ + this.listeners.forEach((listener) => listener(event)); + + /** Clear the `once` queue */ + this.listenersOncer.forEach((listener) => listener(event)); + this.listenersOncer = []; + } + + pipe = (te: TypedEvent): Disposable => { + return this.on((e) => te.emit(e)); + } +} \ No newline at end of file diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss new file mode 100644 index 000000000..234f82eb9 --- /dev/null +++ b/src/client/views/ContextMenu.scss @@ -0,0 +1,34 @@ +.contextMenu-cont { + position: absolute; + display: flex; + z-index: 1000; + box-shadow: #AAAAAA .2vw .2vw .4vw; +} + +.contextMenu-item { + width: 10vw; + height: 4vh; + background: #DDDDDD; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + transition: all .1s; +} + +.contextMenu-item:hover { + transition: all .1s; + background: #AAAAAA +} + +.contextMenu-description { + font-size: 1.5vw; + text-align: left; + width: 8vw; +} \ No newline at end of file diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx new file mode 100644 index 000000000..4f26a75d2 --- /dev/null +++ b/src/client/views/ContextMenu.tsx @@ -0,0 +1,68 @@ +import React = require("react"); +import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem"; +import { observable } from "mobx"; +import { observer } from "mobx-react"; +import "./ContextMenu.scss" + +@observer +export class ContextMenu extends React.Component { + static Instance: ContextMenu + + @observable private _items: Array = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }]; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _display: string = "none"; + + private ref: React.RefObject; + + constructor(props: Readonly<{}>) { + super(props); + + this.ref = React.createRef() + + ContextMenu.Instance = this; + } + + clearItems() { + this._items = [] + this._display = "none" + } + + addItem(item: ContextMenuProps) { + if (this._items.indexOf(item) === -1) { + this._items.push(item); + } + } + + getItems() { + return this._items; + } + + displayMenu(x: number, y: number) { + this._pageX = x + this._pageY = y + + this._display = "flex" + } + + intersects = (x: number, y: number): boolean => { + if (this.ref.current && this._display !== "none") { + if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) { + if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) { + return true; + } + } + } + return false; + } + + render() { + return ( +
+ {this._items.map(prop => { + return + })} +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx new file mode 100644 index 000000000..8f00f8b3d --- /dev/null +++ b/src/client/views/ContextMenuItem.tsx @@ -0,0 +1,17 @@ +import React = require("react"); +import { ContextMenu } from "./ContextMenu"; + +export interface ContextMenuProps { + description: string; + event: (e: React.MouseEvent) => void; +} + +export class ContextMenuItem extends React.Component { + render() { + return ( +
+
{this.props.description}
+
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss new file mode 100644 index 000000000..e8b93a18b --- /dev/null +++ b/src/client/views/DocumentDecorations.scss @@ -0,0 +1,32 @@ +#documentDecorations-container { + position: absolute; + display: grid; + z-index: 1000; + grid-template-rows: 20px 1fr 20px; + grid-template-columns: 20px 1fr 20px; + pointer-events: none; + #documentDecorations-centerCont { + background: none; + } + .documentDecorations-resizer { + pointer-events: auto; + background: lightblue; + opacity: 0.4; + } + #documentDecorations-topLeftResizer, + #documentDecorations-bottomRightResizer { + cursor: nwse-resize; + } + #documentDecorations-topRightResizer, + #documentDecorations-bottomLeftResizer { + cursor: nesw-resize; + } + #documentDecorations-topResizer, + #documentDecorations-bottomResizer { + cursor: ns-resize; + } + #documentDecorations-leftResizer, + #documentDecorations-rightResizer { + cursor: ew-resize; + } +} \ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx new file mode 100644 index 000000000..8a94bff36 --- /dev/null +++ b/src/client/views/DocumentDecorations.tsx @@ -0,0 +1,153 @@ +import { observable, computed } from "mobx"; +import React = require("react"); +import { SelectionManager } from "../util/SelectionManager"; +import { observer } from "mobx-react"; +import './DocumentDecorations.scss' +import { CollectionFreeFormView } from "./collections/CollectionFreeFormView"; + +@observer +export class DocumentDecorations extends React.Component { + static Instance: DocumentDecorations + private _resizer = "" + private _isPointerDown = false; + @observable private _opacity = 1; + + constructor(props: Readonly<{}>) { + super(props) + + DocumentDecorations.Instance = this + } + + @computed + get Bounds(): { x: number, y: number, b: number, r: number } { + return SelectionManager.SelectedDocuments().reduce((bounds, element) => { + if (element.props.ContainingCollectionView != undefined && + !(element.props.ContainingCollectionView instanceof CollectionFreeFormView)) { + return bounds; + } + var spt = element.TransformToScreenPoint(0, 0); + var bpt = element.TransformToScreenPoint(element.width, element.height); + return { + x: Math.min(spt.ScreenX, bounds.x), y: Math.min(spt.ScreenY, bounds.y), + r: Math.max(bpt.ScreenX, bounds.r), b: Math.max(bpt.ScreenY, bounds.b) + } + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + } + + @computed + get opacity(): number { + return this._opacity + } + + set opacity(o: number) { + this._opacity = Math.min(Math.max(0, o), 1) + } + + onPointerDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + this._isPointerDown = true; + this._resizer = e.currentTarget.id; + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + } + + onPointerMove = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + if (!this._isPointerDown) { + return; + } + + let dX = 0, dY = 0, dW = 0, dH = 0; + + switch (this._resizer) { + case "": + break; + case "documentDecorations-topLeftResizer": + dX = -1 + dY = -1 + dW = -(e.movementX) + dH = -(e.movementY) + break; + case "documentDecorations-topRightResizer": + dW = e.movementX + dY = -1 + dH = -(e.movementY) + break; + case "documentDecorations-topResizer": + dY = -1 + dH = -(e.movementY) + break; + case "documentDecorations-bottomLeftResizer": + dX = -1 + dW = -(e.movementX) + dH = e.movementY + break; + case "documentDecorations-bottomRightResizer": + dW = e.movementX + dH = e.movementY + break; + case "documentDecorations-bottomResizer": + dH = e.movementY + break; + case "documentDecorations-leftResizer": + dX = -1 + dW = -(e.movementX) + break; + case "documentDecorations-rightResizer": + dW = e.movementX + break; + } + + SelectionManager.SelectedDocuments().forEach(element => { + const rect = element.screenRect; + if (rect.width !== 0) { + let scale = element.width / rect.width; + let actualdW = Math.max(element.width + (dW * scale), 20); + let actualdH = Math.max(element.height + (dH * scale), 20); + element.x += dX * (actualdW - element.width); + element.y += dY * (actualdH - element.height); + element.width = actualdW; + element.height = actualdH; + } + }) + } + + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + if (e.button === 0) { + e.preventDefault(); + this._isPointerDown = false; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + } + + render() { + var bounds = this.Bounds; + return ( +
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+
e.preventDefault()}>
+ +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx new file mode 100644 index 000000000..2e784d3f9 --- /dev/null +++ b/src/client/views/EditableView.tsx @@ -0,0 +1,39 @@ +import React = require('react') +import { observer } from 'mobx-react'; +import { observable, action } from 'mobx'; + +export interface EditableProps { + GetValue(): string; + SetValue(value: string): boolean; + contents: any; +} + +@observer +export class EditableView extends React.Component { + @observable + editing: boolean = false; + + @action + onKeyDown = (e: React.KeyboardEvent) => { + if (e.key == "Enter" && !e.ctrlKey) { + this.props.SetValue(e.currentTarget.value); + this.editing = false; + } else if (e.key == "Escape") { + this.editing = false; + } + } + + render() { + if (this.editing) { + return this.editing = false)} + style={{ width: "100%" }}> + } else { + return ( +
+ {this.props.contents} + +
+ ) + } + } +} \ No newline at end of file diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss new file mode 100644 index 000000000..e73f62904 --- /dev/null +++ b/src/client/views/Main.scss @@ -0,0 +1,31 @@ +html, +body { + width: 100%; + height: 100%; + overflow: hidden; + font-family: 'Hind Siliguri', sans-serif; + margin: 0; +} + +h1 { + font-size: 50px; + position: fixed; + top: 30px; + left: 50%; + transform: translateX(-50%); + color: black; + text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; + z-index: 9999; + font-family: 'Fjalla One', sans-serif; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +p { + margin: 0px; + padding: 0px; +} \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx new file mode 100644 index 000000000..fc6f0a208 --- /dev/null +++ b/src/client/views/Main.tsx @@ -0,0 +1,91 @@ +import { action, configure } from 'mobx'; +import "normalize.css"; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { DocumentDecorations } from './DocumentDecorations'; +import { Documents } from '../documents/Documents'; +import { Document } from '../../fields/Document'; +import { KeyStore, KeyStore as KS } from '../../fields/Key'; +import { ListField } from '../../fields/ListField'; +import { NumberField } from '../../fields/NumberField'; +import { TextField } from '../../fields/TextField'; +import "./Main.scss"; +import { ContextMenu } from './ContextMenu'; +import { DocumentView } from './nodes/DocumentView'; +import { ImageField } from '../../fields/ImageField'; + + +configure({ + enforceActions: "observed" +}); + +const mainNodeCollection = new Array(); +let mainContainer = Documents.DockDocument(mainNodeCollection, { + x: 0, y: 0, title: "main container" +}) + +window.addEventListener("drop", function (e) { + e.preventDefault(); +}, false) +window.addEventListener("dragover", function (e) { + e.preventDefault(); +}, false) +document.addEventListener("pointerdown", action(function (e: PointerEvent) { + if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) { + ContextMenu.Instance.clearItems() + } +}), true) + + +//runInAction(() => +{ + let doc1 = Documents.TextDocument({ title: "hello" }); + let doc2 = doc1.MakeDelegate(); + doc2.Set(KS.X, new NumberField(150)); + doc2.Set(KS.Y, new NumberField(20)); + 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" + }); + doc3.Set(KeyStore.Data, new ImageField); + 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]; + 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 docset2 = new Array(doc4);//, doc1, doc3); + let doc6 = Documents.CollectionDocument(docset2, { + x: 350, y: 100, width: 600, height: 600, title: "docking collection" + }); + let mainNodes = null;// mainContainer.GetFieldT(KeyStore.Data, ListField); + if (!mainNodes) { + mainNodes = new ListField(); + } + // mainNodes.Data.push(doc6); + // mainNodes.Data.push(doc2); + mainNodes.Data.push(doc4); + mainNodes.Data.push(doc3); + // mainNodes.Data.push(doc5); + // mainNodes.Data.push(doc1); + //mainNodes.Data.push(doc2); + //mainNodes.Data.push(doc6); + mainContainer.Set(KeyStore.Data, mainNodes); +} +//} +//); + +ReactDOM.render(( +
+ + + +
), + document.getElementById('root')); \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss new file mode 100644 index 000000000..7c0b512a7 --- /dev/null +++ b/src/client/views/collections/CollectionDockingView.scss @@ -0,0 +1,336 @@ +.collectiondockingview-container { + position: relative; + top: 0; + left: 0; + overflow: hidden; + .lm_controls>li { + opacity: 0.6; + transform: scale(1.2); + } + .lm_maximised .lm_controls .lm_maximise { + opacity: 1; + transform: scale(0.8); + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important; + } + .flexlayout__layout { + left: 0; + top: 0; + right: 0; + bottom: 0; + position: absolute; + overflow: hidden; + } + .flexlayout__splitter { + background-color: black; + } + .flexlayout__splitter:hover { + background-color: #333; + } + .flexlayout__splitter_drag { + border-radius: 5px; + background-color: #444; + z-index: 1000; + } + .flexlayout__outline_rect { + position: absolute; + cursor: move; + border: 2px solid #cfe8ff; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + } + .flexlayout__outline_rect_edge { + cursor: move; + border: 2px solid #b7d1b5; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + } + .flexlayout__edge_rect { + position: absolute; + z-index: 1000; + box-shadow: inset 0 0 5px rgba(0, 0, 0, .2); + background-color: lightgray; + } + .flexlayout__drag_rect { + position: absolute; + cursor: move; + border: 2px solid #aaaaaa; + box-shadow: inset 0 0 60px rgba(0, 0, 0, .3); + border-radius: 5px; + z-index: 1000; + box-sizing: border-box; + background-color: #eeeeee; + opacity: 0.9; + text-align: center; + display: flex; + justify-content: center; + flex-direction: column; + overflow: hidden; + padding: 10px; + word-wrap: break-word; + } + .flexlayout__tabset { + overflow: hidden; + background-color: #222; + box-sizing: border-box; + } + .flexlayout__tab { + overflow: auto; + position: absolute; + box-sizing: border-box; + background-color: #222; + color: black; + } + .flexlayout__tab_button { + cursor: pointer; + padding: 2px 8px 3px 8px; + margin: 2px; + /*box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);*/ + /*border-top-left-radius: 3px;*/ + /*border-top-right-radius: 3px;*/ + float: left; + vertical-align: top; + box-sizing: border-box; + } + .flexlayout__tab_button--selected { + color: #ddd; + background-color: #222; + } + .flexlayout__tab_button--unselected { + color: gray; + } + .flexlayout__tab_button_leading { + display: inline-block; + } + .flexlayout__tab_button_content { + display: inline-block; + } + .flexlayout__tab_button_textbox { + float: left; + border: none; + color: lightgreen; + background-color: #222; + } + .flexlayout__tab_button_textbox:focus { + outline: none; + } + .flexlayout__tab_button_trailing { + display: inline-block; + margin-left: 5px; + margin-top: 3px; + width: 8px; + height: 8px; + } + .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, + .flexlayout__tab_button--selected .flexlayout__tab_button_trailing { + background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; + } + .flexlayout__tab_button_overflow { + float: left; + width: 20px; + height: 15px; + margin-top: 2px; + padding-left: 12px; + border: none; + font-size: 10px; + color: lightgray; + font-family: Arial, sans-serif; + background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left; + } + .flexlayout__tabset_header { + position: absolute; + left: 0; + right: 0; + color: #eee; + background-color: #212121; + padding: 3px 3px 3px 5px; + /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/ + box-sizing: border-box; + } + .flexlayout__tab_header_inner { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 10000px; + } + .flexlayout__tab_header_outer { + background-color: black; + position: absolute; + left: 0; + right: 0; + /*top: 0px;*/ + /*height: 100px;*/ + overflow: hidden; + } + .flexlayout__tabset-selected { + background-image: linear-gradient(#111, #444); + } + .flexlayout__tabset-maximized { + background-image: linear-gradient(#666, #333); + } + .flexlayout__tab_toolbar { + position: absolute; + display: flex; + flex-direction: row-reverse; + align-items: center; + top: 0; + bottom: 0; + right: 0; + } + .flexlayout__tab_toolbar_button-min { + width: 20px; + height: 20px; + border: none; + outline-width: 0; + background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center; + } + .flexlayout__tab_toolbar_button-max { + width: 20px; + height: 20px; + border: none; + outline-width: 0; + background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center; + } + .flexlayout__popup_menu {} + .flexlayout__popup_menu_item { + padding: 2px 10px 2px 10px; + color: #ddd; + } + .flexlayout__popup_menu_item:hover { + background-color: #444444; + } + .flexlayout__popup_menu_container { + box-shadow: inset 0 0 5px rgba(0, 0, 0, .15); + border: 1px solid #555; + background: #222; + border-radius: 3px; + position: absolute; + z-index: 1000; + } + .flexlayout__border_top { + background-color: black; + border-bottom: 1px solid #ddd; + box-sizing: border-box; + overflow: hidden; + } + .flexlayout__border_bottom { + background-color: black; + border-top: 1px solid #333; + box-sizing: border-box; + overflow: hidden; + } + .flexlayout__border_left { + background-color: black; + border-right: 1px solid #333; + box-sizing: border-box; + overflow: hidden; + } + .flexlayout__border_right { + background-color: black; + border-left: 1px solid #333; + box-sizing: border-box; + overflow: hidden; + } + .flexlayout__border_inner_bottom { + display: flex; + } + .flexlayout__border_inner_left { + position: absolute; + white-space: nowrap; + right: 23px; + transform-origin: top right; + transform: rotate(-90deg); + } + .flexlayout__border_inner_right { + position: absolute; + white-space: nowrap; + left: 23px; + transform-origin: top left; + transform: rotate(90deg); + } + .flexlayout__border_button { + background-color: #222; + color: white; + display: inline-block; + white-space: nowrap; + cursor: pointer; + padding: 2px 8px 3px 8px; + margin: 2px; + vertical-align: top; + box-sizing: border-box; + } + .flexlayout__border_button--selected { + color: #ddd; + background-color: #222; + } + .flexlayout__border_button--unselected { + color: gray; + } + .flexlayout__border_button_leading { + float: left; + display: inline; + } + .flexlayout__border_button_content { + display: inline-block; + } + .flexlayout__border_button_textbox { + float: left; + border: none; + color: green; + background-color: #ddd; + } + .flexlayout__border_button_textbox:focus { + outline: none; + } + .flexlayout__border_button_trailing { + display: inline-block; + margin-left: 5px; + margin-top: 3px; + width: 8px; + height: 8px; + } + .flexlayout__border_button:hover .flexlayout__border_button_trailing, + .flexlayout__border_button--selected .flexlayout__border_button_trailing { + background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; + } + .flexlayout__border_toolbar_left { + position: absolute; + display: flex; + flex-direction: column-reverse; + align-items: center; + bottom: 0; + left: 0; + right: 0; + } + .flexlayout__border_toolbar_right { + position: absolute; + display: flex; + flex-direction: column-reverse; + align-items: center; + bottom: 0; + left: 0; + right: 0; + } + .flexlayout__border_toolbar_top { + position: absolute; + display: flex; + flex-direction: row-reverse; + align-items: center; + top: 0; + bottom: 0; + right: 0; + } + .flexlayout__border_toolbar_bottom { + position: absolute; + display: flex; + flex-direction: row-reverse; + align-items: center; + top: 0; + bottom: 0; + right: 0; + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx new file mode 100644 index 000000000..9aee9c10f --- /dev/null +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -0,0 +1,281 @@ +import { observer } from "mobx-react"; +import { KeyStore } from "../../../fields/Key"; +import React = require("react"); +import FlexLayout from "flexlayout-react"; +import { action, observable, computed } from "mobx"; +import { Document } from "../../../fields/Document"; +import { DocumentView } from "../nodes/DocumentView"; +import { ListField } from "../../../fields/ListField"; +import { NumberField } from "../../../fields/NumberField"; +import "./CollectionDockingView.scss" +import 'golden-layout/src/css/goldenlayout-base.css'; +import 'golden-layout/src/css/goldenlayout-dark-theme.css'; +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"; + +@observer +export class CollectionDockingView extends CollectionViewBase { + + private static UseGoldenLayout = true; + public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); } + private _containerRef = React.createRef(); + @computed + private get modelForFlexLayout() { + const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; + const value: Document[] = Document.GetData(fieldKey, ListField, []); + var docs = value.map(doc => { + return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] }; + }); + return FlexLayout.Model.fromJson({ + global: {}, borders: [], + layout: { + "type": "row", + "weight": 100, + "children": docs + } + }); + } + @computed + private get modelForGoldenLayout(): any { + const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; + const value: Document[] = Document.GetData(fieldKey, ListField, []); + var docs = value.map(doc => { + return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } }; + }); + return new GoldenLayout({ + settings: { + selectionEnabled: true + }, content: [{ type: 'row', content: docs }] + }); + } + constructor(props: CollectionViewProps) { + super(props); + } + + componentDidMount: () => void = () => { + if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) { + this.goldenLayoutFactory(); + window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + } + } + componentWillUnmount: () => void = () => { + window.removeEventListener('resize', this.onResize); + } + private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })(); + + + @action + onResize = (event: any) => { + 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 + CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); + } + + @action + onPointerDown = (e: React.PointerEvent): void => { + if (e.button === 2 && this.active) { + e.stopPropagation(); + e.preventDefault(); + } else { + if (e.buttons === 1 && this.active) { + e.stopPropagation(); + } + } + } + + flexLayoutFactory = (node: any): any => { + var component = node.getComponent(); + if (component === "button") { + return ; + } + const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; + const value: Document[] = Document.GetData(fieldKey, ListField, []); + for (var i: number = 0; i < value.length; i++) { + if (value[i].Id === component) { + return (); + } + } + if (component === "text") { + return (
Panel {node.getName()}
); + } + } + + public static myLayout: any = null; + + private static _dragDiv: any = null; + private static _dragParent: HTMLElement | null = null; + private static _dragElement: HTMLDivElement; + private static _dragFakeElement: HTMLDivElement; + public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) { + var newItemConfig = { + type: 'component', + componentName: 'documentViewComponent', + componentState: { doc: dragDoc } + }; + this._dragElement = dragElement; + this._dragParent = dragElement.parentElement; + // bcz: we want to copy this document into the header, not move it there. + // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: + + // - create a temporary invisible div and register that as a DragSource with GoldenLayout + this._dragDiv = document.createElement("div"); + this._dragDiv.style.opacity = 0; + DragManager.Root().appendChild(this._dragDiv); + CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig); + + // - add our document to that div so that GoldenLayout will get the move events its listening for + this._dragDiv.appendChild(this._dragElement); + + // - add a duplicate of our document to the original document's container + // (GoldenLayout will be removing our original one) + this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement; + this._dragParent!.appendChild(this._dragFakeElement); + + // all of this must be undone when the document has been dropped (see tabCreated) + } + + _makeFullScreen: boolean = false; + _maximizedStack: any = null; + public static OpenFullScreen(document: Document) { + var newItemConfig = { + type: 'component', + componentName: 'documentViewComponent', + componentState: { doc: document } + }; + CollectionDockingView.myLayout._makeFullScreen = true; + CollectionDockingView.myLayout.root.contentItems[0].addChild(newItemConfig); + } + public static CloseFullScreen() { + if (CollectionDockingView.myLayout._maximizedStack != null) { + CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click(); + CollectionDockingView.myLayout._maximizedStack = null; + } + } + + // + // Creates a vertical split on the right side of the docking view, and then adds the Document to that split + // + public static AddRightSplit(document: Document) { + var newItemConfig = { + type: 'component', + componentName: 'documentViewComponent', + componentState: { doc: document } + } + let newItemStackConfig = { + type: 'stack', + content: [newItemConfig] + }; + var newContentItem = new CollectionDockingView.myLayout._typeToItem[newItemStackConfig.type](CollectionDockingView.myLayout, newItemStackConfig, parent); + + if (CollectionDockingView.myLayout.root.contentItems[0].isRow) { + var rowlayout = CollectionDockingView.myLayout.root.contentItems[0]; + var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1]; + + lastRowItem.config["width"] *= 0.5; + newContentItem.config["width"] = lastRowItem.config["width"]; + rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true); + rowlayout.callDownwards('setSize'); + } + else { + var collayout = CollectionDockingView.myLayout.root.contentItems[0]; + var newRow = collayout.layoutManager.createContentItem({ type: "row" }, CollectionDockingView.myLayout); + collayout.parent.replaceChild(collayout, newRow); + + newRow.addChild(newContentItem, undefined, true); + newRow.addChild(collayout, 0, true); + + collayout.config["width"] = 50; + newContentItem.config["width"] = 50; + collayout.parent.callDownwards('setSize'); + } + } + goldenLayoutFactory() { + CollectionDockingView.myLayout = this.modelForGoldenLayout; + + var layout = CollectionDockingView.myLayout; + CollectionDockingView.myLayout.on('tabCreated', function (tab: any) { + if (CollectionDockingView._dragDiv) { + CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement); + CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement); + CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement); + DragManager.Root().removeChild(CollectionDockingView._dragDiv); + CollectionDockingView._dragDiv = null; + } + tab.setTitle(tab.contentItem.config.componentState.doc.Title); + tab.closeElement.off('click') //unbind the current click handler + .click(function () { + tab.contentItem.remove(); + }); + }); + + CollectionDockingView.myLayout.on('stackCreated', function (stack: any) { + if (CollectionDockingView.myLayout._makeFullScreen) { + 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_close') //get the close icon + .off('click') //unbind the current click handler + .click(function () { + //if (confirm('really close this?')) { + stack.remove(); + //} + }); + }); + + var me = this; + CollectionDockingView.myLayout.registerComponent('documentViewComponent', function (container: any, state: any) { + // bcz: this is crufty + // calling html() causes a div tag to be added in the DOM with id 'containingDiv'. + // Apparently, we need to wait to allow a live html div element to actually be instantiated. + // After a timeout, we lookup the live html div element and add our React DocumentView to it. + var containingDiv = "component_" + me.nextId(); + container.getElement().html("
"); + setTimeout(function () { + ReactDOM.render(( + + ), + document.getElementById(containingDiv) + ); + if (CollectionDockingView.myLayout._maxstack != null) { + CollectionDockingView.myLayout._maxstack.click(); + } + }, 0); + }); + CollectionDockingView.myLayout.container = this._containerRef.current; + CollectionDockingView.myLayout.init(); + } + + + render() { + 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. + var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1; + var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s; + var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s; + + var chooseLayout = () => { + if (!CollectionDockingView.UseGoldenLayout) + return ; + } + + return ( +
+ +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss new file mode 100644 index 000000000..e9d134e7b --- /dev/null +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -0,0 +1,20 @@ +.collectionfreeformview-container { + position: relative; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + .collectionfreeformview { + position: absolute; + top: 0; + left: 0; + } +} + +.border { + border-style: solid; + box-sizing: border-box; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx new file mode 100644 index 000000000..9cf29d000 --- /dev/null +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -0,0 +1,205 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { action, observable, computed } from "mobx"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { DragManager } from "../../util/DragManager"; +import "./CollectionFreeFormView.scss"; +import { Utils } from "../../../Utils"; +import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Key, KeyStore } from "../../../fields/Key"; +import { Document } from "../../../fields/Document"; +import { ListField } from "../../../fields/ListField"; +import { NumberField } from "../../../fields/NumberField"; +import { Documents } from "../../documents/Documents"; +import { FieldWaiting } from "../../../fields/Field"; + +@observer +export class CollectionFreeFormView extends CollectionViewBase { + public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); } + private _containerRef = React.createRef(); + private _canvasRef = React.createRef(); + private _nodeContainerRef = React.createRef(); + private _lastX: number = 0; + private _lastY: number = 0; + + constructor(props: CollectionViewProps) { + super(props); + } + + @action + drop = (e: Event, de: DragManager.DropEvent) => { + const doc = de.data["document"]; + var me = this; + if (doc instanceof CollectionFreeFormDocumentView) { + if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { + 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); + } + e.stopPropagation(); + } + + componentDidMount() { + if (this._containerRef.current) { + DragManager.MakeDropTarget(this._containerRef.current, { + handlers: { + drop: this.drop + } + }); + } + } + + @action + onPointerDown = (e: React.PointerEvent): void => { + if ((e.button === 2 && this.active) || + !e.defaultPrevented) { + document.removeEventListener("pointermove", this.onPointerMove); + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + this._lastX = e.pageX; + this._lastY = e.pageY; + } + } + + @action + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + e.stopPropagation(); + SelectionManager.DeselectAll(); + } + + @action + onPointerMove = (e: PointerEvent): void => { + var me = this; + if (!e.cancelBubble && this.active) { + e.preventDefault(); + e.stopPropagation(); + let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; + let x = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); + let y = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); + this.props.DocumentForCollection.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); + this.props.DocumentForCollection.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); + } + this._lastX = e.pageX; + this._lastY = e.pageY; + } + + @action + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + + 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; + + var newContainerX = LocalX * deltaScale + Panxx + Xx; + var newContainerY = LocalY * deltaScale + Panyy + Yy; + + let dx = ContainerX - newContainerX; + let dy = ContainerY - newContainerY; + + this.props.DocumentForCollection.Set(KeyStore.Scale, new NumberField(deltaScale)); + this.props.DocumentForCollection.SetData(KeyStore.PanX, Panxx + dx, NumberField); + this.props.DocumentForCollection.SetData(KeyStore.PanY, Panyy + dy, NumberField); + } + + @action + onDrop = (e: React.DragEvent): void => { + e.stopPropagation() + e.preventDefault() + let fReader = new FileReader() + let file = e.dataTransfer.items[0].getAsFile(); + let that = this; + const panx: number = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); + const pany: number = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); + let x = e.pageX - panx + let y = e.pageY - pany + + fReader.addEventListener("load", action("drop", (event) => { + if (fReader.result) { + let url = "" + fReader.result; + let doc = Documents.ImageDocument(url, { + x: x, y: y + }) + let docs = that.props.DocumentForCollection.GetT(KeyStore.Data, ListField); + if (docs != FieldWaiting) { + if (!docs) { + docs = new ListField(); + that.props.DocumentForCollection.Set(KeyStore.Data, docs) + } + docs.Data.push(doc); + } + } + }), false) + + if (file) { + fReader.readAsDataURL(file) + } + } + + onDragOver = (e: React.DragEvent): void => { + } + + @action + bringToFront(doc: CollectionFreeFormDocumentView) { + const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; + + const value: Document[] = Document.GetList(fieldKey, []); + var topmost = value.reduce((topmost, d) => Math.max(d.GetNumber(KeyStore.ZIndex, 0), topmost), -1000); + value.map(d => { + var zind = d.GetNumber(KeyStore.ZIndex, 0); + if (zind != topmost - 1 - (topmost - zind) && d != doc.props.Document) { + d.SetData(KeyStore.ZIndex, topmost - 1 - (topmost - zind), NumberField); + } + }) + + if (doc.props.Document.GetNumber(KeyStore.ZIndex, 0) != 0) { + doc.props.Document.SetData(KeyStore.ZIndex, 0, NumberField); + } + } + + render() { + const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; + const value: Document[] = Document.GetList(fieldKey, []); + const panx: number = Document.GetNumber(KeyStore.PanX, 0); + const pany: number = Document.GetNumber(KeyStore.PanY, 0); + const currScale: number = Document.GetNumber(KeyStore.Scale, 1); + + return ( +
+
e.preventDefault()} + onDrop={this.onDrop} + onDragOver={this.onDragOver} + ref={this._containerRef}> +
+ +
+ {value.map(doc => { + return (); + })} +
+
+
+
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss new file mode 100644 index 000000000..707b44db6 --- /dev/null +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -0,0 +1,108 @@ +.Resizer { + box-sizing: border-box; + background: #000; + opacity: 0.5; + z-index: 1; + background-clip: padding-box; + &.horizontal { + height: 11px; + margin: -5px 0; + border-top: 5px solid rgba(255, 255, 255, 0); + border-bottom: 5px solid rgba(255, 255, 255, 0); + cursor: row-resize; + width: 100%; + &:hover { + border-top: 5px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid rgba(0, 0, 0, 0.5); + } + } + &.vertical { + width: 11px; + margin: 0 -5px; + border-left: 5px solid rgba(255, 255, 255, 0); + border-right: 5px solid rgba(255, 255, 255, 0); + cursor: col-resize; + &:hover { + border-left: 5px solid rgba(0, 0, 0, 0.5); + border-right: 5px solid rgba(0, 0, 0, 0.5); + } + } + &:hover { + -webkit-transition: all 2s ease; + transition: all 2s ease; + } +} + +.vertical { + section { + width: 100vh; + height: 100vh; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + } + header { + padding: 1rem; + background: #eee; + } + footer { + padding: 1rem; + background: #eee; + } +} + +.horizontal { + section { + width: 100vh; + height: 100vh; + display: flex; + flex-direction: column; + } + header { + padding: 1rem; + background: #eee; + } + footer { + padding: 1rem; + background: #eee; + } +} + +.parent { + width: 100%; + height: 100%; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.header { + background: #aaa; + height: 3rem; + line-height: 3rem; +} + +.wrapper { + background: #ffa; + margin: 5rem; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx new file mode 100644 index 000000000..2d5bd6c99 --- /dev/null +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -0,0 +1,144 @@ +import React = require("react") +import ReactTable, { ReactTableDefaults, CellInfo, ComponentPropsGetterRC, ComponentPropsGetterR } from "react-table"; +import { observer } from "mobx-react"; +import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import "react-table/react-table.css" +import { observable, action, computed } from "mobx"; +import SplitPane from "react-split-pane" +import "./CollectionSchemaView.scss" +import { ScrollBox } from "../../util/ScrollBox"; +import { CollectionViewBase } from "./CollectionViewBase"; +import { DocumentView } from "../nodes/DocumentView"; +import { EditableView } from "../EditableView"; +import { CompileScript, ToField } from "../../util/Scripting"; +import { KeyStore as KS, Key } from "../../../fields/Key"; +import { Document } from "../../../fields/Document"; +import { Field } from "../../../fields/Field"; + +@observer +export class CollectionSchemaView extends CollectionViewBase { + public static LayoutString() { return CollectionViewBase.LayoutString("CollectionSchemaView"); } + + @observable + selectedIndex = 0; + + renderCell = (rowProps: CellInfo) => { + let props: FieldViewProps = { + doc: rowProps.value[0], + fieldKey: rowProps.value[1], + DocumentViewForField: undefined + } + let contents = ( + + ) + return ( + { + let field = props.doc.Get(props.fieldKey); + if (field && field instanceof Field) { + return field.ToScriptString(); + } + return field || ""; + }} SetValue={(value: string) => { + let script = CompileScript(value); + if (!script.compiled) { + return false; + } + let field = script(); + if (field instanceof Field) { + props.doc.Set(props.fieldKey, field); + return true; + } else { + let dataField = ToField(field); + if (dataField) { + props.doc.Set(props.fieldKey, dataField); + return true; + } + } + return false; + }}> + ) + } + + private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { + const that = this; + if (!rowInfo) { + return {}; + } + return { + onClick: action((e: React.MouseEvent, handleOriginal: Function) => { + that.selectedIndex = rowInfo.index; + const doc: Document = rowInfo.original; + console.log("Row clicked: ", doc.Title) + + if (handleOriginal) { + handleOriginal() + } + }), + style: { + background: rowInfo.index == this.selectedIndex ? "#00afec" : "white", + color: rowInfo.index == this.selectedIndex ? "white" : "black" + } + }; + } + + onPointerDown = (e: React.PointerEvent) => { + let target = e.target as HTMLElement; + if (target.tagName == "SPAN" && target.className.includes("Resizer")) { + e.stopPropagation(); + } + if (e.button === 2 && this.active) { + e.stopPropagation(); + e.preventDefault(); + } else { + if (e.buttons === 1 && this.active) { + e.stopPropagation(); + } + } + } + + render() { + const { DocumentForCollection: Document, CollectionFieldKey: fieldKey } = this.props; + const children = Document.GetList(fieldKey, []); + const columns = Document.GetList(KS.ColumnsKey, + [KS.Title, KS.Data, KS.Author]) + let content; + if (this.selectedIndex != -1) { + content = ( + + ) + } else { + content =
+ } + return ( +
+ + + { + return ( + { + Header: col.Name, + accessor: (doc: Document) => [doc, col], + id: col.Id + }) + })} + column={{ + ...ReactTableDefaults.column, + Cell: this.renderCell + }} + getTrProps={this.getTrProps} + /> + + {content} + +
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx new file mode 100644 index 000000000..09e8ec729 --- /dev/null +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -0,0 +1,57 @@ +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { Opt } from "../../../fields/Field"; +import { Key, KeyStore } from "../../../fields/Key"; +import { ListField } from "../../../fields/ListField"; +import { SelectionManager } from "../../util/SelectionManager"; +import { ContextMenu } from "../ContextMenu"; +import React = require("react"); +import { DocumentView } from "../nodes/DocumentView"; +import { CollectionDockingView } from "./CollectionDockingView"; +import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; + + +export interface CollectionViewProps { + CollectionFieldKey: Key; + DocumentForCollection: Document; + ContainingDocumentView: Opt; +} + +export const COLLECTION_BORDER_WIDTH = 2; + +@observer +export class CollectionViewBase extends React.Component { + + public static LayoutString(collectionType: string) { + return `<${collectionType} DocumentForCollection={Document} CollectionFieldKey={DataKey} ContainingDocumentView={DocumentView}/>`; + } + @computed + 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); + return isSelected || childSelected || topMost; + } + @action + addDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array()) + value.push(doc); + } + + @action + removeDocument = (doc: Document): void => { + //TODO This won't create the field if it doesn't already exist + const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array()) + if (value.indexOf(doc) !== -1) { + value.splice(value.indexOf(doc), 1) + + SelectionManager.DeselectAll() + ContextMenu.Instance.clearItems() + } + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx new file mode 100644 index 000000000..1d53cedc4 --- /dev/null +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -0,0 +1,223 @@ +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Key, KeyStore } from "../../../fields/Key"; +import { NumberField } from "../../../fields/NumberField"; +import { DragManager } from "../../util/DragManager"; +import { SelectionManager } from "../../util/SelectionManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; +import { ContextMenu } from "../ContextMenu"; +import "./NodeView.scss"; +import React = require("react"); +import { DocumentView, DocumentViewProps } from "./DocumentView"; + + +@observer +export class CollectionFreeFormDocumentView extends DocumentView { + private _contextMenuCanOpen = false; + private _downX: number = 0; + private _downY: number = 0; + + constructor(props: DocumentViewProps) { + super(props); + } + get screenRect(): ClientRect | DOMRect { + if (this._mainCont.current) { + return this._mainCont.current.getBoundingClientRect(); + } + return new DOMRect(); + } + + @computed + get x(): number { + return this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); + } + + @computed + get y(): number { + return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); + } + + set x(x: number) { + this.props.Document.SetData(KeyStore.X, x, NumberField) + } + + set y(y: number) { + this.props.Document.SetData(KeyStore.Y, y, NumberField) + } + + @computed + get transform(): string { + return `translate(${this.x}px, ${this.y}px)`; + } + + @computed + get width(): number { + return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0)); + } + + set width(w: number) { + this.props.Document.SetData(KeyStore.Width, w, NumberField) + } + + @computed + get height(): number { + return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0)); + } + + set height(h: number) { + this.props.Document.SetData(KeyStore.Height, h, NumberField) + } + + @computed + get zIndex(): number { + return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0)); + } + + set zIndex(h: number) { + this.props.Document.SetData(KeyStore.ZIndex, h, NumberField) + } + + @action + dragComplete = (e: DragManager.DragCompleteEvent) => { + } + + @computed + get active(): boolean { + return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined || + this.props.ContainingCollectionView.active; + } + + @computed + get topMost(): boolean { + return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView; + } + + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + var me = this; + if (e.shiftKey && e.buttons === 1) { + CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document); + e.stopPropagation(); + return; + } + this._contextMenuCanOpen = e.button == 2; + if (this.active && !e.isDefaultPrevented()) { + e.stopPropagation(); + if (e.buttons === 2) { + e.preventDefault(); + } + document.removeEventListener("pointermove", this.onPointerMove) + document.addEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp) + document.addEventListener("pointerup", this.onPointerUp); + } + } + + onPointerMove = (e: PointerEvent): void => { + if (e.cancelBubble) { + this._contextMenuCanOpen = false; + return; + } + if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { + this._contextMenuCanOpen = false; + if (this._mainCont.current != null && !this.topMost) { + this._contextMenuCanOpen = false; + const rect = this.screenRect; + let dragData: { [id: string]: any } = {}; + dragData["document"] = this; + dragData["xOffset"] = e.x - rect.left; + dragData["yOffset"] = e.y - rect.top; + DragManager.StartDrag(this._mainCont.current, dragData, { + handlers: { + dragComplete: this.dragComplete, + }, + hideSource: true + }) + } + } + e.stopPropagation(); + e.preventDefault(); + } + + onPointerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onPointerMove) + document.removeEventListener("pointerup", this.onPointerUp) + e.stopPropagation(); + if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + } + + openRight = (e: React.MouseEvent): void => { + CollectionDockingView.AddRightSplit(this.props.Document); + } + + deleteClicked = (e: React.MouseEvent): void => { + if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) { + this.props.ContainingCollectionView.removeDocument(this.props.Document) + } + } + @action + fullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.OpenFullScreen(this.props.Document); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + } + @action + closeFullScreenClicked = (e: React.MouseEvent): void => { + CollectionDockingView.CloseFullScreen(); + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + } + + @action + onContextMenu = (e: React.MouseEvent): void => { + if (!SelectionManager.IsSelected(this)) { + return; + } + e.preventDefault() + + if (!this._contextMenuCanOpen) { + return; + } + + if (this.topMost) { + ContextMenu.Instance.clearItems() + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + } + else { + // DocumentViews should stop propogation of this event + e.stopPropagation(); + + ContextMenu.Instance.clearItems(); + ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) + ContextMenu.Instance.addItem({ description: "Open Right", event: this.openRight }) + ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }) + ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) + SelectionManager.SelectDoc(this, e.ctrlKey); + } + } + + render() { + var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; + return ( +
+ + +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx new file mode 100644 index 000000000..730ce62f2 --- /dev/null +++ b/src/client/views/nodes/DocumentView.tsx @@ -0,0 +1,153 @@ +import { action, computed } from "mobx"; +import { observer } from "mobx-react"; +import { Document } from "../../../fields/Document"; +import { Opt, FieldWaiting } from "../../../fields/Field"; +import { Key, KeyStore } from "../../../fields/Key"; +import { ListField } from "../../../fields/ListField"; +import { NumberField } from "../../../fields/NumberField"; +import { TextField } from "../../../fields/TextField"; +import { Utils } from "../../../Utils"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; +import { CollectionSchemaView } from "../collections/CollectionSchemaView"; +import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase"; +import { FormattedTextBox } from "../nodes/FormattedTextBox"; +import { ImageBox } from "../nodes/ImageBox"; +import "./NodeView.scss"; +import React = require("react"); +const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? + +export interface DocumentViewProps { + Document: Document; + DocumentView: Opt // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way? + ContainingCollectionView: Opt; +} +@observer +export class DocumentView extends React.Component { + + protected _mainCont = React.createRef(); + get MainContent() { + return this._mainCont; + } + @computed + get layout(): string { + return this.props.Document.GetData(KeyStore.Layout, TextField, String("

Error loading layout data

")); + } + + @computed + get layoutKeys(): Key[] { + return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array()); + } + + @computed + get layoutFields(): Key[] { + return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array()); + } + + // + // returns the cumulative scaling between the document and the screen + // + @computed + public get ScalingToScreenSpace(): number { + if (this.props.ContainingCollectionView != undefined && + this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) { + let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1)); + return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss; + } + return 1; + } + + // + // Converts a coordinate in the screen space of the app into a local document coordinate. + // + 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 ? + this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) : + { LocalX: screenX, LocalY: screenY }; + let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH; + let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH; + + var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); + var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); + // CollectionDockingViews change the location of their children frames without using a Dash transformation. + // They also ignore any transformation that may have been applied to their content document. + // NOTE: this currently assumes CollectionDockingViews aren't nested. + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); + Xx = rx - COLLECTION_BORDER_WIDTH; + Yy = ry - COLLECTION_BORDER_WIDTH; + } + + let Ss = this.props.Document.GetData(KeyStore.Scale, NumberField, Number(1)); + let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)); + let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)); + let LocalX = (ContainerX - (Xx + Panxx)) / Ss; + let LocalY = (ContainerY - (Yy + Panyy)) / Ss; + + return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; + } + + // + // Converts a point in the coordinate space of a document to a screen space coordinate. + // + public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { + + var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); + var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); + // CollectionDockingViews change the location of their children frames without using a Dash transformation. + // They also ignore any transformation that may have been applied to their content document. + // NOTE: this currently assumes CollectionDockingViews aren't nested. + if (this.props.ContainingCollectionView instanceof CollectionDockingView) { + var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); + Xx = rx - COLLECTION_BORDER_WIDTH; + Yy = ry - COLLECTION_BORDER_WIDTH; + } + + let W = COLLECTION_BORDER_WIDTH; + let H = COLLECTION_BORDER_WIDTH; + let parentX = (localX - W) * Ss + (Xx + Panxx) + W; + let parentY = (localY - H) * Ss + (Yy + Panyy) + H; + + // 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 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; + let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy); + parentX = ScreenX; + parentY = ScreenY; + } + return { ScreenX: parentX, ScreenY: parentY }; + } + + + render() { + let bindings = { ...this.props } as any; + for (const key of this.layoutKeys) { + bindings[key.Name + "Key"] = key; // this maps string values of the form Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data + } + for (const key of this.layoutFields) { + 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 ( +
+ { console.log(test) }} + /> +
+ ) + } +} diff --git a/src/client/views/nodes/FieldTextBox.scss b/src/client/views/nodes/FieldTextBox.scss new file mode 100644 index 000000000..b6ce2fabc --- /dev/null +++ b/src/client/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/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx new file mode 100644 index 000000000..12371eb2e --- /dev/null +++ b/src/client/views/nodes/FieldView.tsx @@ -0,0 +1,56 @@ +import React = require("react") +import { observer } from "mobx-react"; +import { computed } from "mobx"; +import { Field, Opt, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { Document } from "../../../fields/Document"; +import { TextField } from "../../../fields/TextField"; +import { NumberField } from "../../../fields/NumberField"; +import { RichTextField } from "../../../fields/RichTextField"; +import { ImageField } from "../../../fields/ImageField"; +import { Key } from "../../../fields/Key"; +import { FormattedTextBox } from "./FormattedTextBox"; +import { ImageBox } from "./ImageBox"; +import { DocumentView } from "./DocumentView"; + +// +// these properties get assigned through the render() method of the DocumentView when it creates this node. +// However, that only happens because the properties are "defined" in the markup for the field view. +// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc. +// +export interface FieldViewProps { + fieldKey: Key; + doc: Document; + DocumentViewForField: Opt +} + +@observer +export class FieldView extends React.Component { + public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; } + @computed + get field(): FieldValue { + const { doc, fieldKey } = this.props; + return doc.Get(fieldKey); + } + render() { + const field = this.field; + if (!field) { + return

{''}

+ } + if (field instanceof TextField) { + return

{field.Data}

+ } + else if (field instanceof RichTextField) { + return + } + else if (field instanceof ImageField) { + return + } + else if (field instanceof NumberField) { + return

{field.Data}

+ } else if (field != FieldWaiting) { + return

{field.GetValue}

+ } else + return

{"Waiting for server..."}

+ } + +} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss new file mode 100644 index 000000000..492367fce --- /dev/null +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -0,0 +1,14 @@ +.ProseMirror { + margin-top: -1em; + width: 100%; + height: 100%; +} + +.ProseMirror:focus { + outline: none !important +} + +.formattedTextBox-cont { + background: white; + padding: 1vw; +} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx new file mode 100644 index 000000000..8bc4c902c --- /dev/null +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -0,0 +1,127 @@ +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, FieldWaiting, FieldValue } from "../../../fields/Field"; +import { SelectionManager } from "../../util/SelectionManager"; +import "./FormattedTextBox.scss"; +import React = require("react") +import { RichTextField } from "../../../fields/RichTextField"; +import { FieldViewProps, FieldView } from "./FieldView"; +import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; + + +// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document +// +// HTML Markup: Key} />"); +// and the node's binding to the specified document KEYNAME as: +// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.])); +// The Jsx parser at run time will bind: +// 'fieldKey' property to the Key stored in LayoutKeys +// and 'doc' property to the document that is being rendered +// +// 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 { + + public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); } + private _ref: React.RefObject; + private _editorView: Opt; + private _reactionDisposer: Opt; + + constructor(props: FieldViewProps) { + super(props); + + this._ref = React.createRef(); + + this.onChange = this.onChange.bind(this); + } + + dispatchTransaction = (tx: Transaction) => { + if (this._editorView) { + const state = this._editorView.state.apply(tx); + this._editorView.updateState(state); + const { doc, fieldKey } = this.props; + doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + } + } + + componentDidMount() { + let state: EditorState; + const { doc, fieldKey } = this.props; + const config = { + schema, + plugins: [ + history(), + keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(baseKeymap) + ] + }; + + let field = doc.GetT(fieldKey, RichTextField); + if (field && field != FieldWaiting) { // bcz: don't think this works + state = EditorState.fromJSON(config, JSON.parse(field.Data)); + } else { + state = EditorState.create(config); + } + if (this._ref.current) { + this._editorView = new EditorView(this._ref.current, { + state, + dispatchTransaction: this.dispatchTransaction + }); + } + + this._reactionDisposer = reaction(() => { + const field = this.props.doc.GetT(this.props.fieldKey, RichTextField); + return field && field != FieldWaiting ? field.Data : undefined; + }, (field) => { + if (field && this._editorView) { + this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))); + } + }) + } + + componentWillUnmount() { + if (this._editorView) { + this._editorView.destroy(); + } + if (this._reactionDisposer) { + this._reactionDisposer(); + } + } + + shouldComponentUpdate() { + return false; + } + + @action + onChange(e: React.ChangeEvent) { + const { fieldKey, doc } = this.props; + doc.SetData(fieldKey, e.target.value, RichTextField); + } + onPointerDown = (e: React.PointerEvent): void => { + let me = this; + if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) { + e.stopPropagation(); + } + } + render() { + return (
) + } +} \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss new file mode 100644 index 000000000..136fda1d0 --- /dev/null +++ b/src/client/views/nodes/ImageBox.scss @@ -0,0 +1,11 @@ + +.imageBox-cont { + padding: 0vw; +} + +.imageBox-button { + padding : 0vw; + border: none; + width : 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx new file mode 100644 index 000000000..ab20f140c --- /dev/null +++ b/src/client/views/nodes/ImageBox.tsx @@ -0,0 +1,92 @@ + +import Lightbox from 'react-image-lightbox'; +import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app +import { SelectionManager } from "../../util/SelectionManager"; +import "./ImageBox.scss"; +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'; + +@observer +export class ImageBox extends React.Component { + + public static LayoutString() { return FieldView.LayoutString("ImageBox"); } + private _ref: React.RefObject; + 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); + + this._ref = React.createRef(); + this.state = { + photoIndex: 0, + isOpen: false, + }; + } + + componentDidMount() { + } + + componentWillUnmount() { + } + + onPointerDown = (e: React.PointerEvent): void => { + if (Date.now() - this._lastTap < 300) { + if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) { + e.stopPropagation(); + this._downX = e.clientX; + this._downY = e.clientY; + document.removeEventListener("pointerup", this.onPointerUp); + document.addEventListener("pointerup", this.onPointerUp); + } + } else { + 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._isOpen = true; + } + e.stopPropagation(); + } + + 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 ( 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 ( +
+ Image not found + {this.lightbox(path)} +
) + } +} \ No newline at end of file diff --git a/src/client/views/nodes/NodeView.scss b/src/client/views/nodes/NodeView.scss new file mode 100644 index 000000000..dac1c0a8e --- /dev/null +++ b/src/client/views/nodes/NodeView.scss @@ -0,0 +1,23 @@ +.node { + position: absolute; + background: #cdcdcd; + overflow: hidden; + &.minimized { + width: 30px; + height: 30px; + } + .top { + background: #232323; + height: 20px; + cursor: pointer; + } + .content { + padding: 20px 20px; + height: auto; + box-sizing: border-box; + } + .scroll-box { + overflow-y: scroll; + height: calc(100% - 20px); + } +} \ No newline at end of file diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts deleted file mode 100644 index ce9a1529d..000000000 --- a/src/documents/Documents.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Document } from "../fields/Document"; -import { Server } from "../Server"; -import { KeyStore } from "../fields/Key"; -import { TextField } from "../fields/TextField"; -import { NumberField } from "../fields/NumberField"; -import { ListField } from "../fields/ListField"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { CollectionSchemaView } from "../views/collections/CollectionSchemaView"; -import { ImageField } from "../fields/ImageField"; -import { ImageBox } from "../views/nodes/ImageBox"; -import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView"; -import { FIELD_ID } from "../fields/Field"; - -interface DocumentOptions { - x?: number; - y?: number; - width?: number; - height?: number; - title?: string; -} - -export namespace Documents { - function setupOptions(doc: Document, options: DocumentOptions): void { - if (options.x) { - doc.SetData(KeyStore.X, options.x, NumberField); - } - if (options.y) { - doc.SetData(KeyStore.Y, options.y, NumberField); - } - if (options.width) { - doc.SetData(KeyStore.Width, options.width, NumberField); - } - if (options.height) { - doc.SetData(KeyStore.Height, options.height, NumberField); - } - if (options.title) { - doc.SetData(KeyStore.Title, options.title, TextField); - } - doc.SetData(KeyStore.Scale, 1, NumberField); - doc.SetData(KeyStore.PanX, 0, NumberField); - doc.SetData(KeyStore.PanY, 0, NumberField); - } - - let textProto: Document; - function GetTextPrototype(): Document { - if (!textProto) { - textProto = new Document(); - textProto.Set(KeyStore.X, new NumberField(0)); - textProto.Set(KeyStore.Y, new NumberField(0)); - textProto.Set(KeyStore.Width, new NumberField(300)); - textProto.Set(KeyStore.Height, new NumberField(150)); - textProto.Set(KeyStore.Layout, new TextField(FormattedTextBox.LayoutString())); - textProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return textProto; - } - - export function TextDocument(options: DocumentOptions = {}): Document { - let doc = GetTextPrototype().MakeDelegate(); - setupOptions(doc, options); - // doc.SetField(KeyStore.Data, new RichTextField()); - return doc; - } - - let schemaProto: Document; - function GetSchemaPrototype(): Document { - if (!schemaProto) { - schemaProto = new Document(); - schemaProto.Set(KeyStore.X, new NumberField(0)); - schemaProto.Set(KeyStore.Y, new NumberField(0)); - schemaProto.Set(KeyStore.Width, new NumberField(300)); - schemaProto.Set(KeyStore.Height, new NumberField(150)); - schemaProto.Set(KeyStore.Layout, new TextField(CollectionSchemaView.LayoutString())); - schemaProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return schemaProto; - } - - export function SchemaDocument(documents: Array, options: DocumentOptions = {}): Document { - let doc = GetSchemaPrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new ListField(documents)); - return doc; - } - - - let dockProto: Document; - function GetDockPrototype(): Document { - if (!dockProto) { - dockProto = new Document(); - dockProto.Set(KeyStore.X, new NumberField(0)); - dockProto.Set(KeyStore.Y, new NumberField(0)); - dockProto.Set(KeyStore.Width, new NumberField(300)); - dockProto.Set(KeyStore.Height, new NumberField(150)); - dockProto.Set(KeyStore.Layout, new TextField(CollectionDockingView.LayoutString())); - dockProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return dockProto; - } - - export function DockDocument(documents: Array, options: DocumentOptions = {}): Document { - let doc = GetDockPrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new ListField(documents)); - return doc; - } - - - let imageProtoId: FIELD_ID; - function GetImagePrototype(): 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)); - imageProto.Set(KeyStore.Height, new NumberField(300)); - imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString())); - // imageProto.SetField(KeyStore.Layout, new TextField('
')); - imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - Server.AddDocument(imageProto); - return imageProto; - } - return Server.GetField(imageProtoId) as Document; - } - - export function ImageDocument(url: string, options: DocumentOptions = {}): Document { - let doc = GetImagePrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new ImageField(new URL(url))); - Server.AddDocument(doc); - var sdoc = Server.GetField(doc.Id) as Document; - return sdoc; - } - - let collectionProto: Document; - function GetCollectionPrototype(): Document { - if (!collectionProto) { - collectionProto = new Document(); - collectionProto.Set(KeyStore.X, new NumberField(0)); - collectionProto.Set(KeyStore.Y, new NumberField(0)); - collectionProto.Set(KeyStore.Scale, new NumberField(1)); - collectionProto.Set(KeyStore.PanX, new NumberField(0)); - collectionProto.Set(KeyStore.PanY, new NumberField(0)); - collectionProto.Set(KeyStore.Width, new NumberField(300)); - collectionProto.Set(KeyStore.Height, new NumberField(300)); - collectionProto.Set(KeyStore.Layout, new TextField(CollectionFreeFormView.LayoutString())); - collectionProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); - } - return collectionProto; - } - - export function CollectionDocument(documents: Array, options: DocumentOptions = {}): Document { - let doc = GetCollectionPrototype().MakeDelegate(); - setupOptions(doc, options); - doc.Set(KeyStore.Data, new ListField(documents)); - return doc; - } -} \ No newline at end of file diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 546c89e04..6f9752a8e 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -5,7 +5,7 @@ import { ObservableMap, computed, action, observable } from "mobx"; import { TextField } from "./TextField"; import { ListField } from "./ListField"; import { findDOMNode } from "react-dom"; -import { Server } from "../Server"; +import { Server } from "../client/Server"; export class Document extends Field { public fields: ObservableMap> = new ObservableMap(); diff --git a/src/server/index.js b/src/server/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/src/util/DragManager.ts b/src/util/DragManager.ts deleted file mode 100644 index 63d6a88f8..000000000 --- a/src/util/DragManager.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { Opt } from "../fields/Field"; -import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; -import { DocumentDecorations } from "../DocumentDecorations"; -import { SelectionManager } from "./SelectionManager"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { Document } from "../fields/Document"; - -export namespace DragManager { - export function Root() { - const root = document.getElementById("root"); - if (!root) { - throw new Error("No root element found"); - } - return root; - } - - let dragDiv: HTMLDivElement; - - export enum DragButtons { - Left = 1, Right = 2, Both = Left | Right - } - - interface DragOptions { - handlers: DragHandlers; - - hideSource: boolean | (() => boolean); - } - - export interface DragDropDisposer { - (): void; - } - - export class DragCompleteEvent { - } - - export interface DragHandlers { - dragComplete: (e: DragCompleteEvent) => void; - } - - export interface DropOptions { - handlers: DropHandlers; - } - - export class DropEvent { - constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { } - } - - export interface DropHandlers { - drop: (e: Event, de: DropEvent) => void; - } - - export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer { - if ("canDrop" in element.dataset) { - throw new Error("Element is already droppable, can't make it droppable again"); - } - element.dataset["canDrop"] = "true"; - const handler = (e: Event) => { - const ce = e as CustomEvent; - options.handlers.drop(e, ce.detail); - }; - element.addEventListener("dashOnDrop", handler); - return () => { - element.removeEventListener("dashOnDrop", handler); - delete element.dataset["canDrop"] - }; - } - - - let _lastPointerX: number = 0; - let _lastPointerY: number = 0; - export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) { - if (!dragDiv) { - dragDiv = document.createElement("div"); - DragManager.Root().appendChild(dragDiv); - } - const w = ele.offsetWidth, h = ele.offsetHeight; - const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, scaleY = rect.height / h; - let x = rect.left, y = rect.top; - // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; - let dragElement = ele.cloneNode(true) as HTMLElement; - dragElement.style.opacity = "0.7"; - dragElement.style.position = "absolute"; - dragElement.style.transformOrigin = "0 0"; - dragElement.style.zIndex = "1000"; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; - dragDiv.appendChild(dragElement); - _lastPointerX = dragData["xOffset"] + rect.left; - _lastPointerY = dragData["yOffset"] + rect.top; - - let hideSource = false; - if (typeof options.hideSource === "boolean") { - hideSource = options.hideSource; - } else { - hideSource = options.hideSource(); - } - const wasHidden = ele.hidden; - if (hideSource) { - ele.hidden = true; - } - - const moveHandler = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - x += e.clientX - _lastPointerX; _lastPointerX = e.clientX; - y += e.clientY - _lastPointerY; _lastPointerY = e.clientY; - dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; - }; - const upHandler = (e: PointerEvent) => { - document.removeEventListener("pointermove", moveHandler, true); - document.removeEventListener("pointerup", upHandler); - FinishDrag(dragElement, e, options, dragData); - if (hideSource && !wasHidden) { - ele.hidden = false; - } - }; - document.addEventListener("pointermove", moveHandler, true); - document.addEventListener("pointerup", upHandler); - } - - function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) { - dragDiv.removeChild(dragEle); - const target = document.elementFromPoint(e.x, e.y); - if (!target) { - return; - } - target.dispatchEvent(new CustomEvent("dashOnDrop", { - bubbles: true, - detail: { - x: e.x, - y: e.y, - data: dragData - } - })); - options.handlers.dragComplete({}); - } -} \ No newline at end of file diff --git a/src/util/Scripting.ts b/src/util/Scripting.ts deleted file mode 100644 index 0e83ab4f0..000000000 --- a/src/util/Scripting.ts +++ /dev/null @@ -1,58 +0,0 @@ -// import * as ts from "typescript" -let ts = (window as any).ts; -import { Opt, Field } from "../fields/Field"; -import { Document as DocumentImport } from "../fields/Document"; -import { NumberField as NumberFieldImport, NumberField } from "../fields/NumberField"; -import { ImageField as ImageFieldImport } from "../fields/ImageField"; -import { TextField as TextFieldImport, TextField } from "../fields/TextField"; -import { RichTextField as RichTextFieldImport } from "../fields/RichTextField"; -import { KeyStore as KeyStoreImport } from "../fields/Key"; - -export interface ExecutableScript { - (): any; - - compiled: boolean; -} - -function ExecScript(script: string, diagnostics: Opt): ExecutableScript { - const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); - - let func: () => Opt; - if (compiled) { - func = function (): Opt { - let KeyStore = KeyStoreImport; - let Document = DocumentImport; - let NumberField = NumberFieldImport; - let TextField = TextFieldImport; - let ImageField = ImageFieldImport; - let RichTextField = RichTextFieldImport; - let window = undefined; - let document = undefined; - let retVal = eval(script); - - return retVal; - }; - } else { - func = () => undefined; - } - - return Object.assign(func, - { - compiled - }); -} - -export function CompileScript(script: string): ExecutableScript { - let result = (window as any).ts.transpileModule(script, {}) - - return ExecScript(result.outputText, result.diagnostics); -} - -export function ToField(data: any): Opt { - if (typeof data == "string") { - return new TextField(data); - } else if (typeof data == "number") { - return new NumberField(data); - } - return undefined; -} \ No newline at end of file diff --git a/src/util/ScrollBox.tsx b/src/util/ScrollBox.tsx deleted file mode 100644 index b6b088170..000000000 --- a/src/util/ScrollBox.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React = require("react") - -export class ScrollBox extends React.Component { - onWheel = (e: React.WheelEvent) => { - if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom - e.stopPropagation(); - } - } - - render() { - return ( -
- {this.props.children} -
- ) - } -} \ No newline at end of file diff --git a/src/util/SelectionManager.ts b/src/util/SelectionManager.ts deleted file mode 100644 index 0759ae110..000000000 --- a/src/util/SelectionManager.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; -import { observable, action } from "mobx"; - -export namespace SelectionManager { - class Manager { - @observable - SelectedDocuments: Array = []; - - @action - SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { - // if doc is not in SelectedDocuments, add it - if (!ctrlPressed) { - manager.SelectedDocuments = []; - } - - if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc) - } - } - } - - const manager = new Manager; - - export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { - manager.SelectDoc(doc, ctrlPressed) - } - - export function IsSelected(doc: CollectionFreeFormDocumentView): boolean { - return manager.SelectedDocuments.indexOf(doc) !== -1; - } - - export function DeselectAll(): void { - manager.SelectedDocuments = [] - } - - export function SelectedDocuments(): Array { - return manager.SelectedDocuments; - } -} \ No newline at end of file diff --git a/src/util/TypedEvent.ts b/src/util/TypedEvent.ts deleted file mode 100644 index 0714a7f5c..000000000 --- a/src/util/TypedEvent.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface Listener { - (event: T): any; -} - -export interface Disposable { - dispose(): void; -} - -/** passes through events as they happen. You will not get events from before you start listening */ -export class TypedEvent { - private listeners: Listener[] = []; - private listenersOncer: Listener[] = []; - - on = (listener: Listener): Disposable => { - this.listeners.push(listener); - return { - dispose: () => this.off(listener) - }; - } - - once = (listener: Listener): void => { - this.listenersOncer.push(listener); - } - - off = (listener: Listener) => { - var callbackIndex = this.listeners.indexOf(listener); - if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); - } - - emit = (event: T) => { - /** Update any general listeners */ - this.listeners.forEach((listener) => listener(event)); - - /** Clear the `once` queue */ - this.listenersOncer.forEach((listener) => listener(event)); - this.listenersOncer = []; - } - - pipe = (te: TypedEvent): Disposable => { - return this.on((e) => te.emit(e)); - } -} \ No newline at end of file diff --git a/src/views/ContextMenu.scss b/src/views/ContextMenu.scss deleted file mode 100644 index 234f82eb9..000000000 --- a/src/views/ContextMenu.scss +++ /dev/null @@ -1,34 +0,0 @@ -.contextMenu-cont { - position: absolute; - display: flex; - z-index: 1000; - box-shadow: #AAAAAA .2vw .2vw .4vw; -} - -.contextMenu-item { - width: 10vw; - height: 4vh; - background: #DDDDDD; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - transition: all .1s; -} - -.contextMenu-item:hover { - transition: all .1s; - background: #AAAAAA -} - -.contextMenu-description { - font-size: 1.5vw; - text-align: left; - width: 8vw; -} \ No newline at end of file diff --git a/src/views/ContextMenu.tsx b/src/views/ContextMenu.tsx deleted file mode 100644 index 4f26a75d2..000000000 --- a/src/views/ContextMenu.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React = require("react"); -import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem"; -import { observable } from "mobx"; -import { observer } from "mobx-react"; -import "./ContextMenu.scss" - -@observer -export class ContextMenu extends React.Component { - static Instance: ContextMenu - - @observable private _items: Array = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }]; - @observable private _pageX: number = 0; - @observable private _pageY: number = 0; - @observable private _display: string = "none"; - - private ref: React.RefObject; - - constructor(props: Readonly<{}>) { - super(props); - - this.ref = React.createRef() - - ContextMenu.Instance = this; - } - - clearItems() { - this._items = [] - this._display = "none" - } - - addItem(item: ContextMenuProps) { - if (this._items.indexOf(item) === -1) { - this._items.push(item); - } - } - - getItems() { - return this._items; - } - - displayMenu(x: number, y: number) { - this._pageX = x - this._pageY = y - - this._display = "flex" - } - - intersects = (x: number, y: number): boolean => { - if (this.ref.current && this._display !== "none") { - if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) { - if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) { - return true; - } - } - } - return false; - } - - render() { - return ( -
- {this._items.map(prop => { - return - })} -
- ) - } -} \ No newline at end of file diff --git a/src/views/ContextMenuItem.tsx b/src/views/ContextMenuItem.tsx deleted file mode 100644 index 8f00f8b3d..000000000 --- a/src/views/ContextMenuItem.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React = require("react"); -import { ContextMenu } from "./ContextMenu"; - -export interface ContextMenuProps { - description: string; - event: (e: React.MouseEvent) => void; -} - -export class ContextMenuItem extends React.Component { - render() { - return ( -
-
{this.props.description}
-
- ) - } -} \ No newline at end of file diff --git a/src/views/EditableView.tsx b/src/views/EditableView.tsx deleted file mode 100644 index 2e784d3f9..000000000 --- a/src/views/EditableView.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React = require('react') -import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; - -export interface EditableProps { - GetValue(): string; - SetValue(value: string): boolean; - contents: any; -} - -@observer -export class EditableView extends React.Component { - @observable - editing: boolean = false; - - @action - onKeyDown = (e: React.KeyboardEvent) => { - if (e.key == "Enter" && !e.ctrlKey) { - this.props.SetValue(e.currentTarget.value); - this.editing = false; - } else if (e.key == "Escape") { - this.editing = false; - } - } - - render() { - if (this.editing) { - return this.editing = false)} - style={{ width: "100%" }}> - } else { - return ( -
- {this.props.contents} - -
- ) - } - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionDockingView.scss b/src/views/collections/CollectionDockingView.scss deleted file mode 100644 index db924b57f..000000000 --- a/src/views/collections/CollectionDockingView.scss +++ /dev/null @@ -1,336 +0,0 @@ -.collectiondockingview-container { - position: relative; - top: 0; - left: 0; - overflow: hidden; - .lm_controls>li { - opacity: 0.6; - transform: scale(1.2); - } - .lm_maximised .lm_controls .lm_maximise { - opacity: 1; - transform: scale(0.8); - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important; - } - .flexlayout__layout { - left: 0; - top: 0; - right: 0; - bottom: 0; - position: absolute; - overflow: hidden; - } - .flexlayout__splitter { - background-color: black; - } - .flexlayout__splitter:hover { - background-color: #333; - } - .flexlayout__splitter_drag { - border-radius: 5px; - background-color: #444; - z-index: 1000; - } - .flexlayout__outline_rect { - position: absolute; - cursor: move; - border: 2px solid #cfe8ff; - box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); - border-radius: 5px; - z-index: 1000; - box-sizing: border-box; - } - .flexlayout__outline_rect_edge { - cursor: move; - border: 2px solid #b7d1b5; - box-shadow: inset 0 0 60px rgba(0, 0, 0, .2); - border-radius: 5px; - z-index: 1000; - box-sizing: border-box; - } - .flexlayout__edge_rect { - position: absolute; - z-index: 1000; - box-shadow: inset 0 0 5px rgba(0, 0, 0, .2); - background-color: lightgray; - } - .flexlayout__drag_rect { - position: absolute; - cursor: move; - border: 2px solid #aaaaaa; - box-shadow: inset 0 0 60px rgba(0, 0, 0, .3); - border-radius: 5px; - z-index: 1000; - box-sizing: border-box; - background-color: #eeeeee; - opacity: 0.9; - text-align: center; - display: flex; - justify-content: center; - flex-direction: column; - overflow: hidden; - padding: 10px; - word-wrap: break-word; - } - .flexlayout__tabset { - overflow: hidden; - background-color: #222; - box-sizing: border-box; - } - .flexlayout__tab { - overflow: auto; - position: absolute; - box-sizing: border-box; - background-color: #222; - color: black; - } - .flexlayout__tab_button { - cursor: pointer; - padding: 2px 8px 3px 8px; - margin: 2px; - /*box-shadow: inset 0px 0px 5px rgba(0, 0, 0, .15);*/ - /*border-top-left-radius: 3px;*/ - /*border-top-right-radius: 3px;*/ - float: left; - vertical-align: top; - box-sizing: border-box; - } - .flexlayout__tab_button--selected { - color: #ddd; - background-color: #222; - } - .flexlayout__tab_button--unselected { - color: gray; - } - .flexlayout__tab_button_leading { - display: inline-block; - } - .flexlayout__tab_button_content { - display: inline-block; - } - .flexlayout__tab_button_textbox { - float: left; - border: none; - color: lightgreen; - background-color: #222; - } - .flexlayout__tab_button_textbox:focus { - outline: none; - } - .flexlayout__tab_button_trailing { - display: inline-block; - margin-left: 5px; - margin-top: 3px; - width: 8px; - height: 8px; - } - .flexlayout__tab_button:hover .flexlayout__tab_button_trailing, - .flexlayout__tab_button--selected .flexlayout__tab_button_trailing { - background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; - } - .flexlayout__tab_button_overflow { - float: left; - width: 20px; - height: 15px; - margin-top: 2px; - padding-left: 12px; - border: none; - font-size: 10px; - color: lightgray; - font-family: Arial, sans-serif; - background: transparent url("../../../node_modules/flexlayout-react/images/more.png") no-repeat left; - } - .flexlayout__tabset_header { - position: absolute; - left: 0; - right: 0; - color: #eee; - background-color: #212121; - padding: 3px 3px 3px 5px; - /*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/ - box-sizing: border-box; - } - .flexlayout__tab_header_inner { - position: absolute; - left: 0; - top: 0; - bottom: 0; - width: 10000px; - } - .flexlayout__tab_header_outer { - background-color: black; - position: absolute; - left: 0; - right: 0; - /*top: 0px;*/ - /*height: 100px;*/ - overflow: hidden; - } - .flexlayout__tabset-selected { - background-image: linear-gradient(#111, #444); - } - .flexlayout__tabset-maximized { - background-image: linear-gradient(#666, #333); - } - .flexlayout__tab_toolbar { - position: absolute; - display: flex; - flex-direction: row-reverse; - align-items: center; - top: 0; - bottom: 0; - right: 0; - } - .flexlayout__tab_toolbar_button-min { - width: 20px; - height: 20px; - border: none; - outline-width: 0; - background: transparent url("../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center; - } - .flexlayout__tab_toolbar_button-max { - width: 20px; - height: 20px; - border: none; - outline-width: 0; - background: transparent url("../../../node_modules/flexlayout-react/images/restore.png") no-repeat center; - } - .flexlayout__popup_menu {} - .flexlayout__popup_menu_item { - padding: 2px 10px 2px 10px; - color: #ddd; - } - .flexlayout__popup_menu_item:hover { - background-color: #444444; - } - .flexlayout__popup_menu_container { - box-shadow: inset 0 0 5px rgba(0, 0, 0, .15); - border: 1px solid #555; - background: #222; - border-radius: 3px; - position: absolute; - z-index: 1000; - } - .flexlayout__border_top { - background-color: black; - border-bottom: 1px solid #ddd; - box-sizing: border-box; - overflow: hidden; - } - .flexlayout__border_bottom { - background-color: black; - border-top: 1px solid #333; - box-sizing: border-box; - overflow: hidden; - } - .flexlayout__border_left { - background-color: black; - border-right: 1px solid #333; - box-sizing: border-box; - overflow: hidden; - } - .flexlayout__border_right { - background-color: black; - border-left: 1px solid #333; - box-sizing: border-box; - overflow: hidden; - } - .flexlayout__border_inner_bottom { - display: flex; - } - .flexlayout__border_inner_left { - position: absolute; - white-space: nowrap; - right: 23px; - transform-origin: top right; - transform: rotate(-90deg); - } - .flexlayout__border_inner_right { - position: absolute; - white-space: nowrap; - left: 23px; - transform-origin: top left; - transform: rotate(90deg); - } - .flexlayout__border_button { - background-color: #222; - color: white; - display: inline-block; - white-space: nowrap; - cursor: pointer; - padding: 2px 8px 3px 8px; - margin: 2px; - vertical-align: top; - box-sizing: border-box; - } - .flexlayout__border_button--selected { - color: #ddd; - background-color: #222; - } - .flexlayout__border_button--unselected { - color: gray; - } - .flexlayout__border_button_leading { - float: left; - display: inline; - } - .flexlayout__border_button_content { - display: inline-block; - } - .flexlayout__border_button_textbox { - float: left; - border: none; - color: green; - background-color: #ddd; - } - .flexlayout__border_button_textbox:focus { - outline: none; - } - .flexlayout__border_button_trailing { - display: inline-block; - margin-left: 5px; - margin-top: 3px; - width: 8px; - height: 8px; - } - .flexlayout__border_button:hover .flexlayout__border_button_trailing, - .flexlayout__border_button--selected .flexlayout__border_button_trailing { - background: transparent url("../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center; - } - .flexlayout__border_toolbar_left { - position: absolute; - display: flex; - flex-direction: column-reverse; - align-items: center; - bottom: 0; - left: 0; - right: 0; - } - .flexlayout__border_toolbar_right { - position: absolute; - display: flex; - flex-direction: column-reverse; - align-items: center; - bottom: 0; - left: 0; - right: 0; - } - .flexlayout__border_toolbar_top { - position: absolute; - display: flex; - flex-direction: row-reverse; - align-items: center; - top: 0; - bottom: 0; - right: 0; - } - .flexlayout__border_toolbar_bottom { - position: absolute; - display: flex; - flex-direction: row-reverse; - align-items: center; - top: 0; - bottom: 0; - right: 0; - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionDockingView.tsx b/src/views/collections/CollectionDockingView.tsx deleted file mode 100644 index adef03357..000000000 --- a/src/views/collections/CollectionDockingView.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { observer } from "mobx-react"; -import { KeyStore } from "../../fields/Key"; -import React = require("react"); -import FlexLayout from "flexlayout-react"; -import { action, observable, computed } from "mobx"; -import { Document } from "../../fields/Document"; -import { DocumentView } from "../nodes/DocumentView"; -import { ListField } from "../../fields/ListField"; -import { NumberField } from "../../fields/NumberField"; -import { SSL_OP_SINGLE_DH_USE } from "constants"; -import "./CollectionDockingView.scss" -import 'golden-layout/src/css/goldenlayout-base.css'; -import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -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"; - -@observer -export class CollectionDockingView extends CollectionViewBase { - - private static UseGoldenLayout = true; - public static LayoutString() { return CollectionViewBase.LayoutString("CollectionDockingView"); } - private _containerRef = React.createRef(); - @computed - private get modelForFlexLayout() { - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetData(fieldKey, ListField, []); - var docs = value.map(doc => { - return { type: 'tabset', weight: 50, selected: 0, children: [{ type: "tab", name: doc.Title, component: doc.Id }] }; - }); - return FlexLayout.Model.fromJson({ - global: {}, borders: [], - layout: { - "type": "row", - "weight": 100, - "children": docs - } - }); - } - @computed - private get modelForGoldenLayout(): any { - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetData(fieldKey, ListField, []); - var docs = value.map(doc => { - return { type: 'component', componentName: 'documentViewComponent', componentState: { doc: doc } }; - }); - return new GoldenLayout({ - settings: { - selectionEnabled: true - }, content: [{ type: 'row', content: docs }] - }); - } - constructor(props: CollectionViewProps) { - super(props); - } - - componentDidMount: () => void = () => { - if (this._containerRef.current && CollectionDockingView.UseGoldenLayout) { - this.goldenLayoutFactory(); - window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window - } - } - componentWillUnmount: () => void = () => { - window.removeEventListener('resize', this.onResize); - } - private nextId = (function () { var _next_id = 0; return function () { return _next_id++; } })(); - - - @action - onResize = (event: any) => { - 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 - CollectionDockingView.myLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height); - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - if (e.button === 2 && this.active) { - e.stopPropagation(); - e.preventDefault(); - } else { - if (e.buttons === 1 && this.active) { - e.stopPropagation(); - } - } - } - - flexLayoutFactory = (node: any): any => { - var component = node.getComponent(); - if (component === "button") { - return ; - } - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetData(fieldKey, ListField, []); - for (var i: number = 0; i < value.length; i++) { - if (value[i].Id === component) { - return (); - } - } - if (component === "text") { - return (
Panel {node.getName()}
); - } - } - - public static myLayout: any = null; - - private static _dragDiv: any = null; - private static _dragParent: HTMLElement | null = null; - private static _dragElement: HTMLDivElement; - private static _dragFakeElement: HTMLDivElement; - public static StartOtherDrag(dragElement: HTMLDivElement, dragDoc: Document) { - var newItemConfig = { - type: 'component', - componentName: 'documentViewComponent', - componentState: { doc: dragDoc } - }; - this._dragElement = dragElement; - this._dragParent = dragElement.parentElement; - // bcz: we want to copy this document into the header, not move it there. - // However, GoldenLayout is setup to move things, so we have to do some kludgy stuff: - - // - create a temporary invisible div and register that as a DragSource with GoldenLayout - this._dragDiv = document.createElement("div"); - this._dragDiv.style.opacity = 0; - DragManager.Root().appendChild(this._dragDiv); - CollectionDockingView.myLayout.createDragSource(this._dragDiv, newItemConfig); - - // - add our document to that div so that GoldenLayout will get the move events its listening for - this._dragDiv.appendChild(this._dragElement); - - // - add a duplicate of our document to the original document's container - // (GoldenLayout will be removing our original one) - this._dragFakeElement = dragElement.cloneNode(true) as HTMLDivElement; - this._dragParent!.appendChild(this._dragFakeElement); - - // all of this must be undone when the document has been dropped (see tabCreated) - } - - _makeFullScreen: boolean = false; - _maximizedStack: any = null; - public static OpenFullScreen(document: Document) { - var newItemConfig = { - type: 'component', - componentName: 'documentViewComponent', - componentState: { doc: document } - }; - CollectionDockingView.myLayout._makeFullScreen = true; - CollectionDockingView.myLayout.root.contentItems[0].addChild(newItemConfig); - } - public static CloseFullScreen() { - if (CollectionDockingView.myLayout._maximizedStack != null) { - CollectionDockingView.myLayout._maximizedStack.header.controlsContainer.find('.lm_close').click(); - CollectionDockingView.myLayout._maximizedStack = null; - } - } - - // - // Creates a vertical split on the right side of the docking view, and then adds the Document to that split - // - public static AddRightSplit(document: Document) { - var newItemConfig = { - type: 'component', - componentName: 'documentViewComponent', - componentState: { doc: document } - } - let newItemStackConfig = { - type: 'stack', - content: [newItemConfig] - }; - var newContentItem = new CollectionDockingView.myLayout._typeToItem[newItemStackConfig.type](CollectionDockingView.myLayout, newItemStackConfig, parent); - - if (CollectionDockingView.myLayout.root.contentItems[0].isRow) { - var rowlayout = CollectionDockingView.myLayout.root.contentItems[0]; - var lastRowItem = rowlayout.contentItems[rowlayout.contentItems.length - 1]; - - lastRowItem.config["width"] *= 0.5; - newContentItem.config["width"] = lastRowItem.config["width"]; - rowlayout.addChild(newContentItem, rowlayout.contentItems.length, true); - rowlayout.callDownwards('setSize'); - } - else { - var collayout = CollectionDockingView.myLayout.root.contentItems[0]; - var newRow = collayout.layoutManager.createContentItem({ type: "row" }, CollectionDockingView.myLayout); - collayout.parent.replaceChild(collayout, newRow); - - newRow.addChild(newContentItem, undefined, true); - newRow.addChild(collayout, 0, true); - - collayout.config["width"] = 50; - newContentItem.config["width"] = 50; - collayout.parent.callDownwards('setSize'); - } - } - goldenLayoutFactory() { - CollectionDockingView.myLayout = this.modelForGoldenLayout; - - var layout = CollectionDockingView.myLayout; - CollectionDockingView.myLayout.on('tabCreated', function (tab: any) { - if (CollectionDockingView._dragDiv) { - CollectionDockingView._dragDiv.removeChild(CollectionDockingView._dragElement); - CollectionDockingView._dragParent!.removeChild(CollectionDockingView._dragFakeElement); - CollectionDockingView._dragParent!.appendChild(CollectionDockingView._dragElement); - DragManager.Root().removeChild(CollectionDockingView._dragDiv); - CollectionDockingView._dragDiv = null; - } - tab.setTitle(tab.contentItem.config.componentState.doc.Title); - tab.closeElement.off('click') //unbind the current click handler - .click(function () { - tab.contentItem.remove(); - }); - }); - - CollectionDockingView.myLayout.on('stackCreated', function (stack: any) { - if (CollectionDockingView.myLayout._makeFullScreen) { - 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_close') //get the close icon - .off('click') //unbind the current click handler - .click(function () { - //if (confirm('really close this?')) { - stack.remove(); - //} - }); - }); - - var me = this; - CollectionDockingView.myLayout.registerComponent('documentViewComponent', function (container: any, state: any) { - // bcz: this is crufty - // calling html() causes a div tag to be added in the DOM with id 'containingDiv'. - // Apparently, we need to wait to allow a live html div element to actually be instantiated. - // After a timeout, we lookup the live html div element and add our React DocumentView to it. - var containingDiv = "component_" + me.nextId(); - container.getElement().html("
"); - setTimeout(function () { - ReactDOM.render(( - - ), - document.getElementById(containingDiv) - ); - if (CollectionDockingView.myLayout._maxstack != null) { - CollectionDockingView.myLayout._maxstack.click(); - } - }, 0); - }); - CollectionDockingView.myLayout.container = this._containerRef.current; - CollectionDockingView.myLayout.init(); - } - - - render() { - 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. - var s = this.props.ContainingDocumentView != undefined ? this.props.ContainingDocumentView!.ScalingToScreenSpace : 1; - var w = Document.GetData(KeyStore.Width, NumberField, Number(0)) / s; - var h = Document.GetData(KeyStore.Height, NumberField, Number(0)) / s; - - var chooseLayout = () => { - if (!CollectionDockingView.UseGoldenLayout) - return ; - } - - return ( -
- -
- ); - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.scss b/src/views/collections/CollectionFreeFormView.scss deleted file mode 100644 index e9d134e7b..000000000 --- a/src/views/collections/CollectionFreeFormView.scss +++ /dev/null @@ -1,20 +0,0 @@ -.collectionfreeformview-container { - position: relative; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden; - .collectionfreeformview { - position: absolute; - top: 0; - left: 0; - } -} - -.border { - border-style: solid; - box-sizing: border-box; - width: 100%; - height: 100%; -} \ No newline at end of file diff --git a/src/views/collections/CollectionFreeFormView.tsx b/src/views/collections/CollectionFreeFormView.tsx deleted file mode 100644 index 612f9acbe..000000000 --- a/src/views/collections/CollectionFreeFormView.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { observer } from "mobx-react"; -import { Key, KeyStore } from "../../fields/Key"; -import React = require("react"); -import { action, observable, computed } from "mobx"; -import { Document } from "../../fields/Document"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { ListField } from "../../fields/ListField"; -import { NumberField } from "../../fields/NumberField"; -import { SSL_OP_SINGLE_DH_USE } from "constants"; -import { Documents } from "../../documents/Documents"; -import { DragManager } from "../../util/DragManager"; -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 { - public static LayoutString() { return CollectionViewBase.LayoutString("CollectionFreeFormView"); } - private _containerRef = React.createRef(); - private _canvasRef = React.createRef(); - private _nodeContainerRef = React.createRef(); - private _lastX: number = 0; - private _lastY: number = 0; - - constructor(props: CollectionViewProps) { - super(props); - } - - @action - drop = (e: Event, de: DragManager.DropEvent) => { - const doc = de.data["document"]; - var me = this; - if (doc instanceof CollectionFreeFormDocumentView) { - if (doc.props.ContainingCollectionView && doc.props.ContainingCollectionView !== this) { - 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); - } - e.stopPropagation(); - } - - componentDidMount() { - if (this._containerRef.current) { - DragManager.MakeDropTarget(this._containerRef.current, { - handlers: { - drop: this.drop - } - }); - } - } - - @action - onPointerDown = (e: React.PointerEvent): void => { - if ((e.button === 2 && this.active) || - !e.defaultPrevented) { - document.removeEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - this._lastX = e.pageX; - this._lastY = e.pageY; - } - } - - @action - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - e.stopPropagation(); - SelectionManager.DeselectAll(); - } - - @action - onPointerMove = (e: PointerEvent): void => { - var me = this; - if (!e.cancelBubble && this.active) { - e.preventDefault(); - e.stopPropagation(); - let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; - let x = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); - let y = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); - this.props.DocumentForCollection.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); - this.props.DocumentForCollection.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); - } - this._lastX = e.pageX; - this._lastY = e.pageY; - } - - @action - onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); - - 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; - - var newContainerX = LocalX * deltaScale + Panxx + Xx; - var newContainerY = LocalY * deltaScale + Panyy + Yy; - - let dx = ContainerX - newContainerX; - let dy = ContainerY - newContainerY; - - this.props.DocumentForCollection.Set(KeyStore.Scale, new NumberField(deltaScale)); - this.props.DocumentForCollection.SetData(KeyStore.PanX, Panxx + dx, NumberField); - this.props.DocumentForCollection.SetData(KeyStore.PanY, Panyy + dy, NumberField); - } - - @action - onDrop = (e: React.DragEvent): void => { - e.stopPropagation() - e.preventDefault() - let fReader = new FileReader() - let file = e.dataTransfer.items[0].getAsFile(); - let that = this; - const panx: number = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); - const pany: number = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); - let x = e.pageX - panx - let y = e.pageY - pany - - fReader.addEventListener("load", action("drop", (event) => { - if (fReader.result) { - let url = "" + fReader.result; - let doc = Documents.ImageDocument(url, { - x: x, y: y - }) - let docs = that.props.DocumentForCollection.GetT(KeyStore.Data, ListField); - if (docs != FieldWaiting) { - if (!docs) { - docs = new ListField(); - that.props.DocumentForCollection.Set(KeyStore.Data, docs) - } - docs.Data.push(doc); - } - } - }), false) - - if (file) { - fReader.readAsDataURL(file) - } - } - - onDragOver = (e: React.DragEvent): void => { - } - - @action - bringToFront(doc: CollectionFreeFormDocumentView) { - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - - const value: Document[] = Document.GetList(fieldKey, []); - var topmost = value.reduce((topmost, d) => Math.max(d.GetNumber(KeyStore.ZIndex, 0), topmost), -1000); - value.map(d => { - var zind = d.GetNumber(KeyStore.ZIndex, 0); - if (zind != topmost - 1 - (topmost - zind) && d != doc.props.Document) { - d.SetData(KeyStore.ZIndex, topmost - 1 - (topmost - zind), NumberField); - } - }) - - if (doc.props.Document.GetNumber(KeyStore.ZIndex, 0) != 0) { - doc.props.Document.SetData(KeyStore.ZIndex, 0, NumberField); - } - } - - render() { - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetList(fieldKey, []); - const panx: number = Document.GetNumber(KeyStore.PanX, 0); - const pany: number = Document.GetNumber(KeyStore.PanY, 0); - const currScale: number = Document.GetNumber(KeyStore.Scale, 1); - - return ( -
-
e.preventDefault()} - onDrop={this.onDrop} - onDragOver={this.onDragOver} - ref={this._containerRef}> -
- -
- {value.map(doc => { - return (); - })} -
-
-
-
- ); - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionSchemaView.scss b/src/views/collections/CollectionSchemaView.scss deleted file mode 100644 index 707b44db6..000000000 --- a/src/views/collections/CollectionSchemaView.scss +++ /dev/null @@ -1,108 +0,0 @@ -.Resizer { - box-sizing: border-box; - background: #000; - opacity: 0.5; - z-index: 1; - background-clip: padding-box; - &.horizontal { - height: 11px; - margin: -5px 0; - border-top: 5px solid rgba(255, 255, 255, 0); - border-bottom: 5px solid rgba(255, 255, 255, 0); - cursor: row-resize; - width: 100%; - &:hover { - border-top: 5px solid rgba(0, 0, 0, 0.5); - border-bottom: 5px solid rgba(0, 0, 0, 0.5); - } - } - &.vertical { - width: 11px; - margin: 0 -5px; - border-left: 5px solid rgba(255, 255, 255, 0); - border-right: 5px solid rgba(255, 255, 255, 0); - cursor: col-resize; - &:hover { - border-left: 5px solid rgba(0, 0, 0, 0.5); - border-right: 5px solid rgba(0, 0, 0, 0.5); - } - } - &:hover { - -webkit-transition: all 2s ease; - transition: all 2s ease; - } -} - -.vertical { - section { - width: 100vh; - height: 100vh; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; - } - header { - padding: 1rem; - background: #eee; - } - footer { - padding: 1rem; - background: #eee; - } -} - -.horizontal { - section { - width: 100vh; - height: 100vh; - display: flex; - flex-direction: column; - } - header { - padding: 1rem; - background: #eee; - } - footer { - padding: 1rem; - background: #eee; - } -} - -.parent { - width: 100%; - height: 100%; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-orient: vertical; - -webkit-box-direction: normal; - -webkit-flex-direction: column; - -ms-flex-direction: column; - flex-direction: column; -} - -.header { - background: #aaa; - height: 3rem; - line-height: 3rem; -} - -.wrapper { - background: #ffa; - margin: 5rem; - -webkit-box-flex: 1; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; -} \ No newline at end of file diff --git a/src/views/collections/CollectionSchemaView.tsx b/src/views/collections/CollectionSchemaView.tsx deleted file mode 100644 index 6f591a1bc..000000000 --- a/src/views/collections/CollectionSchemaView.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import React = require("react") -import ReactTable, { ReactTableDefaults, CellInfo, ComponentPropsGetterRC, ComponentPropsGetterR } from "react-table"; -import { observer } from "mobx-react"; -import { KeyStore as KS, Key } from "../../fields/Key"; -import { Document } from "../../fields/Document"; -import { FieldView, FieldViewProps } from "../nodes/FieldView"; -import "react-table/react-table.css" -import { observable, action, computed } from "mobx"; -import SplitPane from "react-split-pane" -import "./CollectionSchemaView.scss" -import { ScrollBox } from "../../util/ScrollBox"; -import { CollectionViewBase } from "./CollectionViewBase"; -import { DocumentView } from "../nodes/DocumentView"; -import { EditableView } from "../EditableView"; -import { CompileScript, ToField } from "../../util/Scripting"; -import { Field } from "../../fields/Field"; - -@observer -export class CollectionSchemaView extends CollectionViewBase { - public static LayoutString() { return CollectionViewBase.LayoutString("CollectionSchemaView"); } - - @observable - selectedIndex = 0; - - renderCell = (rowProps: CellInfo) => { - let props: FieldViewProps = { - doc: rowProps.value[0], - fieldKey: rowProps.value[1], - DocumentViewForField: undefined - } - let contents = ( - - ) - return ( - { - let field = props.doc.Get(props.fieldKey); - if (field && field instanceof Field) { - return field.ToScriptString(); - } - return field || ""; - }} SetValue={(value: string) => { - let script = CompileScript(value); - if (!script.compiled) { - return false; - } - let field = script(); - if (field instanceof Field) { - props.doc.Set(props.fieldKey, field); - return true; - } else { - let dataField = ToField(field); - if (dataField) { - props.doc.Set(props.fieldKey, dataField); - return true; - } - } - return false; - }}> - ) - } - - private getTrProps: ComponentPropsGetterR = (state, rowInfo) => { - const that = this; - if (!rowInfo) { - return {}; - } - return { - onClick: action((e: React.MouseEvent, handleOriginal: Function) => { - that.selectedIndex = rowInfo.index; - const doc: Document = rowInfo.original; - console.log("Row clicked: ", doc.Title) - - if (handleOriginal) { - handleOriginal() - } - }), - style: { - background: rowInfo.index == this.selectedIndex ? "#00afec" : "white", - color: rowInfo.index == this.selectedIndex ? "white" : "black" - } - }; - } - - onPointerDown = (e: React.PointerEvent) => { - let target = e.target as HTMLElement; - if (target.tagName == "SPAN" && target.className.includes("Resizer")) { - e.stopPropagation(); - } - if (e.button === 2 && this.active) { - e.stopPropagation(); - e.preventDefault(); - } else { - if (e.buttons === 1 && this.active) { - e.stopPropagation(); - } - } - } - - render() { - const { DocumentForCollection: Document, CollectionFieldKey: fieldKey } = this.props; - const children = Document.GetList(fieldKey, []); - const columns = Document.GetList(KS.ColumnsKey, - [KS.Title, KS.Data, KS.Author]) - let content; - if (this.selectedIndex != -1) { - content = ( - - ) - } else { - content =
- } - return ( -
- - - { - return ( - { - Header: col.Name, - accessor: (doc: Document) => [doc, col], - id: col.Id - }) - })} - column={{ - ...ReactTableDefaults.column, - Cell: this.renderCell - }} - getTrProps={this.getTrProps} - /> - - {content} - -
- ) - } -} \ No newline at end of file diff --git a/src/views/collections/CollectionViewBase.tsx b/src/views/collections/CollectionViewBase.tsx deleted file mode 100644 index 35d938d43..000000000 --- a/src/views/collections/CollectionViewBase.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { action, computed } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../fields/Document"; -import { Opt } from "../../fields/Field"; -import { Key, KeyStore } from "../../fields/Key"; -import { ListField } from "../../fields/ListField"; -import { SelectionManager } from "../../util/SelectionManager"; -import { ContextMenu } from "../ContextMenu"; -import React = require("react"); -import { DocumentView } from "../nodes/DocumentView"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; - - -export interface CollectionViewProps { - CollectionFieldKey: Key; - DocumentForCollection: Document; - ContainingDocumentView: Opt; -} - -export const COLLECTION_BORDER_WIDTH = 2; - -@observer -export class CollectionViewBase extends React.Component { - - public static LayoutString(collectionType: string) { - return `<${collectionType} DocumentForCollection={Document} CollectionFieldKey={DataKey} ContainingDocumentView={DocumentView}/>`; - } - @computed - 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); - return isSelected || childSelected || topMost; - } - @action - addDocument = (doc: Document): void => { - //TODO This won't create the field if it doesn't already exist - const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array()) - value.push(doc); - } - - @action - removeDocument = (doc: Document): void => { - //TODO This won't create the field if it doesn't already exist - const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array()) - if (value.indexOf(doc) !== -1) { - value.splice(value.indexOf(doc), 1) - - SelectionManager.DeselectAll() - ContextMenu.Instance.clearItems() - } - } - -} \ No newline at end of file diff --git a/src/views/nodes/CollectionFreeFormDocumentView.tsx b/src/views/nodes/CollectionFreeFormDocumentView.tsx deleted file mode 100644 index e0bb459e9..000000000 --- a/src/views/nodes/CollectionFreeFormDocumentView.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import { action, computed } from "mobx"; -import { observer } from "mobx-react"; -import { Key, KeyStore } from "../../fields/Key"; -import { NumberField } from "../../fields/NumberField"; -import { DragManager } from "../../util/DragManager"; -import { SelectionManager } from "../../util/SelectionManager"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; -import { ContextMenu } from "../ContextMenu"; -import "./NodeView.scss"; -import React = require("react"); -import { DocumentView, DocumentViewProps } from "./DocumentView"; - - -@observer -export class CollectionFreeFormDocumentView extends DocumentView { - private _contextMenuCanOpen = false; - private _downX: number = 0; - private _downY: number = 0; - - constructor(props: DocumentViewProps) { - super(props); - } - get screenRect(): ClientRect | DOMRect { - if (this._mainCont.current) { - return this._mainCont.current.getBoundingClientRect(); - } - return new DOMRect(); - } - - @computed - get x(): number { - return this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); - } - - @computed - get y(): number { - return this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); - } - - set x(x: number) { - this.props.Document.SetData(KeyStore.X, x, NumberField) - } - - set y(y: number) { - this.props.Document.SetData(KeyStore.Y, y, NumberField) - } - - @computed - get transform(): string { - return `translate(${this.x}px, ${this.y}px)`; - } - - @computed - get width(): number { - return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0)); - } - - set width(w: number) { - this.props.Document.SetData(KeyStore.Width, w, NumberField) - } - - @computed - get height(): number { - return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0)); - } - - set height(h: number) { - this.props.Document.SetData(KeyStore.Height, h, NumberField) - } - - @computed - get zIndex(): number { - return this.props.Document.GetData(KeyStore.ZIndex, NumberField, Number(0)); - } - - set zIndex(h: number) { - this.props.Document.SetData(KeyStore.ZIndex, h, NumberField) - } - - @action - dragComplete = (e: DragManager.DragCompleteEvent) => { - } - - @computed - get active(): boolean { - return SelectionManager.IsSelected(this) || this.props.ContainingCollectionView === undefined || - this.props.ContainingCollectionView.active; - } - - @computed - get topMost(): boolean { - return this.props.ContainingCollectionView == undefined || this.props.ContainingCollectionView instanceof CollectionDockingView; - } - - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - var me = this; - if (e.shiftKey && e.buttons === 1) { - CollectionDockingView.StartOtherDrag(this._mainCont.current!, this.props.Document); - e.stopPropagation(); - return; - } - this._contextMenuCanOpen = e.button == 2; - if (this.active && !e.isDefaultPrevented()) { - e.stopPropagation(); - if (e.buttons === 2) { - e.preventDefault(); - } - document.removeEventListener("pointermove", this.onPointerMove) - document.addEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp) - document.addEventListener("pointerup", this.onPointerUp); - } - } - - onPointerMove = (e: PointerEvent): void => { - if (e.cancelBubble) { - this._contextMenuCanOpen = false; - return; - } - if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - this._contextMenuCanOpen = false; - if (this._mainCont.current != null && !this.topMost) { - this._contextMenuCanOpen = false; - const rect = this.screenRect; - let dragData: { [id: string]: any } = {}; - dragData["document"] = this; - dragData["xOffset"] = e.x - rect.left; - dragData["yOffset"] = e.y - rect.top; - DragManager.StartDrag(this._mainCont.current, dragData, { - handlers: { - dragComplete: this.dragComplete, - }, - hideSource: true - }) - } - } - e.stopPropagation(); - e.preventDefault(); - } - - onPointerUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onPointerMove) - document.removeEventListener("pointerup", this.onPointerUp) - e.stopPropagation(); - if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) { - SelectionManager.SelectDoc(this, e.ctrlKey); - } - } - - openRight = (e: React.MouseEvent): void => { - CollectionDockingView.AddRightSplit(this.props.Document); - } - - deleteClicked = (e: React.MouseEvent): void => { - if (this.props.ContainingCollectionView instanceof CollectionFreeFormView) { - this.props.ContainingCollectionView.removeDocument(this.props.Document) - } - } - @action - fullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.OpenFullScreen(this.props.Document); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) - } - @action - closeFullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.CloseFullScreen(); - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) - } - - @action - onContextMenu = (e: React.MouseEvent): void => { - if (!SelectionManager.IsSelected(this)) { - return; - } - e.preventDefault() - - if (!this._contextMenuCanOpen) { - return; - } - - if (this.topMost) { - ContextMenu.Instance.clearItems() - ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) - } - else { - // DocumentViews should stop propogation of this event - e.stopPropagation(); - - ContextMenu.Instance.clearItems(); - ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked }) - ContextMenu.Instance.addItem({ description: "Open Right", event: this.openRight }) - ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked }) - ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15) - SelectionManager.SelectDoc(this, e.ctrlKey); - } - } - - render() { - var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; - return ( -
- - -
- ); - } -} \ No newline at end of file diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx deleted file mode 100644 index 39be54a4d..000000000 --- a/src/views/nodes/DocumentView.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { action, computed } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../fields/Document"; -import { Opt, FieldWaiting } from "../../fields/Field"; -import { Key, KeyStore } from "../../fields/Key"; -import { ListField } from "../../fields/ListField"; -import { NumberField } from "../../fields/NumberField"; -import { TextField } from "../../fields/TextField"; -import { Utils } from "../../Utils"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { CollectionFreeFormView } from "../collections/CollectionFreeFormView"; -import { CollectionSchemaView } from "../collections/CollectionSchemaView"; -import { CollectionViewBase, COLLECTION_BORDER_WIDTH } from "../collections/CollectionViewBase"; -import { FormattedTextBox } from "../nodes/FormattedTextBox"; -import { ImageBox } from "../nodes/ImageBox"; -import "./NodeView.scss"; -import React = require("react"); -const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? - -export interface DocumentViewProps { - Document: Document; - DocumentView: Opt // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way? - ContainingCollectionView: Opt; -} -@observer -export class DocumentView extends React.Component { - - protected _mainCont = React.createRef(); - get MainContent() { - return this._mainCont; - } - @computed - get layout(): string { - return this.props.Document.GetData(KeyStore.Layout, TextField, String("

Error loading layout data

")); - } - - @computed - get layoutKeys(): Key[] { - return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array()); - } - - @computed - get layoutFields(): Key[] { - return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array()); - } - - // - // returns the cumulative scaling between the document and the screen - // - @computed - public get ScalingToScreenSpace(): number { - if (this.props.ContainingCollectionView != undefined && - this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) { - let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1)); - return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss; - } - return 1; - } - - // - // Converts a coordinate in the screen space of the app into a local document coordinate. - // - 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 ? - this.props.ContainingCollectionView.props.ContainingDocumentView.TransformToLocalPoint(screenX, screenY) : - { LocalX: screenX, LocalY: screenY }; - let ContainerX: number = parentX - COLLECTION_BORDER_WIDTH; - let ContainerY: number = parentY - COLLECTION_BORDER_WIDTH; - - var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); - var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); - // CollectionDockingViews change the location of their children frames without using a Dash transformation. - // They also ignore any transformation that may have been applied to their content document. - // NOTE: this currently assumes CollectionDockingViews aren't nested. - if (this.props.ContainingCollectionView instanceof CollectionDockingView) { - var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); - Xx = rx - COLLECTION_BORDER_WIDTH; - Yy = ry - COLLECTION_BORDER_WIDTH; - } - - let Ss = this.props.Document.GetData(KeyStore.Scale, NumberField, Number(1)); - let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)); - let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)); - let LocalX = (ContainerX - (Xx + Panxx)) / Ss; - let LocalY = (ContainerY - (Yy + Panyy)) / Ss; - - return { LocalX, Ss, Panxx, Xx, LocalY, Panyy, Yy, ContainerX, ContainerY }; - } - - // - // Converts a point in the coordinate space of a document to a screen space coordinate. - // - public TransformToScreenPoint(localX: number, localY: number, Ss: number = 1, Panxx: number = 0, Panyy: number = 0): { ScreenX: number, ScreenY: number } { - - var Xx = this.props.Document.GetData(KeyStore.X, NumberField, Number(0)); - var Yy = this.props.Document.GetData(KeyStore.Y, NumberField, Number(0)); - // CollectionDockingViews change the location of their children frames without using a Dash transformation. - // They also ignore any transformation that may have been applied to their content document. - // NOTE: this currently assumes CollectionDockingViews aren't nested. - if (this.props.ContainingCollectionView instanceof CollectionDockingView) { - var { translateX: rx, translateY: ry } = Utils.GetScreenTransform(this.MainContent.current!); - Xx = rx - COLLECTION_BORDER_WIDTH; - Yy = ry - COLLECTION_BORDER_WIDTH; - } - - let W = COLLECTION_BORDER_WIDTH; - let H = COLLECTION_BORDER_WIDTH; - let parentX = (localX - W) * Ss + (Xx + Panxx) + W; - let parentY = (localY - H) * Ss + (Yy + Panyy) + H; - - // 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 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; - let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy); - parentX = ScreenX; - parentY = ScreenY; - } - return { ScreenX: parentX, ScreenY: parentY }; - } - - - render() { - let bindings = { ...this.props } as any; - for (const key of this.layoutKeys) { - bindings[key.Name + "Key"] = key; // this maps string values of the form Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data - } - for (const key of this.layoutFields) { - 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 ( -
- { console.log(test) }} - /> -
- ) - } -} diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss deleted file mode 100644 index b6ce2fabc..000000000 --- a/src/views/nodes/FieldTextBox.scss +++ /dev/null @@ -1,14 +0,0 @@ -.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 deleted file mode 100644 index 3a7652284..000000000 --- a/src/views/nodes/FieldView.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React = require("react") -import { Document } from "../../fields/Document"; -import { observer } from "mobx-react"; -import { computed } from "mobx"; -import { Field, Opt, FieldWaiting, FieldValue } from "../../fields/Field"; -import { TextField } from "../../fields/TextField"; -import { NumberField } from "../../fields/NumberField"; -import { RichTextField } from "../../fields/RichTextField"; -import { FormattedTextBox } from "./FormattedTextBox"; -import { ImageField } from "../../fields/ImageField"; -import { ImageBox } from "./ImageBox"; -import { Key } from "../../fields/Key"; -import { DocumentView } from "./DocumentView"; - -// -// these properties get assigned through the render() method of the DocumentView when it creates this node. -// However, that only happens because the properties are "defined" in the markup for the field view. -// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc. -// -export interface FieldViewProps { - fieldKey: Key; - doc: Document; - DocumentViewForField: Opt -} - -@observer -export class FieldView extends React.Component { - public static LayoutString(fieldType: string) { return `<${fieldType} doc={Document} DocumentViewForField={DocumentView} fieldKey={DataKey} />`; } - @computed - get field(): FieldValue { - const { doc, fieldKey } = this.props; - return doc.Get(fieldKey); - } - render() { - const field = this.field; - if (!field) { - return

{''}

- } - if (field instanceof TextField) { - return

{field.Data}

- } - else if (field instanceof RichTextField) { - return - } - else if (field instanceof ImageField) { - return - } - else if (field instanceof NumberField) { - return

{field.Data}

- } else if (field != FieldWaiting) { - return

{field.GetValue}

- } else - return

{"Waiting for server..."}

- } - -} \ No newline at end of file diff --git a/src/views/nodes/FormattedTextBox.scss b/src/views/nodes/FormattedTextBox.scss deleted file mode 100644 index 492367fce..000000000 --- a/src/views/nodes/FormattedTextBox.scss +++ /dev/null @@ -1,14 +0,0 @@ -.ProseMirror { - margin-top: -1em; - width: 100%; - height: 100%; -} - -.ProseMirror:focus { - outline: none !important -} - -.formattedTextBox-cont { - background: white; - padding: 1vw; -} \ No newline at end of file diff --git a/src/views/nodes/FormattedTextBox.tsx b/src/views/nodes/FormattedTextBox.tsx deleted file mode 100644 index 6d0e117cc..000000000 --- a/src/views/nodes/FormattedTextBox.tsx +++ /dev/null @@ -1,127 +0,0 @@ -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, FieldWaiting, FieldValue } from "../../fields/Field"; -import { SelectionManager } from "../../util/SelectionManager"; -import "./FormattedTextBox.scss"; -import React = require("react") -import { RichTextField } from "../../fields/RichTextField"; -import { FieldViewProps, FieldView } from "./FieldView"; -import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; - - -// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document -// -// HTML Markup: Key} />"); -// and the node's binding to the specified document KEYNAME as: -// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.])); -// The Jsx parser at run time will bind: -// 'fieldKey' property to the Key stored in LayoutKeys -// and 'doc' property to the document that is being rendered -// -// 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 { - - public static LayoutString() { return FieldView.LayoutString("FormattedTextBox"); } - private _ref: React.RefObject; - private _editorView: Opt; - private _reactionDisposer: Opt; - - constructor(props: FieldViewProps) { - super(props); - - this._ref = React.createRef(); - - this.onChange = this.onChange.bind(this); - } - - dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); - const { doc, fieldKey } = this.props; - doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); - } - } - - componentDidMount() { - let state: EditorState; - const { doc, fieldKey } = this.props; - const config = { - schema, - plugins: [ - history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), - keymap(baseKeymap) - ] - }; - - let field = doc.GetT(fieldKey, RichTextField); - if (field && field != FieldWaiting) { // bcz: don't think this works - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); - } - if (this._ref.current) { - this._editorView = new EditorView(this._ref.current, { - state, - dispatchTransaction: this.dispatchTransaction - }); - } - - this._reactionDisposer = reaction(() => { - const field = this.props.doc.GetT(this.props.fieldKey, RichTextField); - return field && field != FieldWaiting ? field.Data : undefined; - }, (field) => { - if (field && this._editorView) { - this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))); - } - }) - } - - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); - } - if (this._reactionDisposer) { - this._reactionDisposer(); - } - } - - shouldComponentUpdate() { - return false; - } - - @action - onChange(e: React.ChangeEvent) { - const { fieldKey, doc } = this.props; - doc.SetData(fieldKey, e.target.value, RichTextField); - } - onPointerDown = (e: React.PointerEvent): void => { - let me = this; - if (e.buttons === 1 && me.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(me.props.DocumentViewForField)) { - e.stopPropagation(); - } - } - render() { - return (
) - } -} \ No newline at end of file diff --git a/src/views/nodes/ImageBox.scss b/src/views/nodes/ImageBox.scss deleted file mode 100644 index 136fda1d0..000000000 --- a/src/views/nodes/ImageBox.scss +++ /dev/null @@ -1,11 +0,0 @@ - -.imageBox-cont { - padding: 0vw; -} - -.imageBox-button { - padding : 0vw; - border: none; - width : 100%; - height: 100%; -} \ No newline at end of file diff --git a/src/views/nodes/ImageBox.tsx b/src/views/nodes/ImageBox.tsx deleted file mode 100644 index 123c76d19..000000000 --- a/src/views/nodes/ImageBox.tsx +++ /dev/null @@ -1,92 +0,0 @@ - -import Lightbox from 'react-image-lightbox'; -import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app -import { SelectionManager } from "../../util/SelectionManager"; -import "./ImageBox.scss"; -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'; - -@observer -export class ImageBox extends React.Component { - - public static LayoutString() { return FieldView.LayoutString("ImageBox"); } - private _ref: React.RefObject; - 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); - - this._ref = React.createRef(); - this.state = { - photoIndex: 0, - isOpen: false, - }; - } - - componentDidMount() { - } - - componentWillUnmount() { - } - - onPointerDown = (e: React.PointerEvent): void => { - if (Date.now() - this._lastTap < 300) { - if (e.buttons === 1 && this.props.DocumentViewForField instanceof CollectionFreeFormDocumentView && SelectionManager.IsSelected(this.props.DocumentViewForField)) { - e.stopPropagation(); - this._downX = e.clientX; - this._downY = e.clientY; - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointerup", this.onPointerUp); - } - } else { - 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._isOpen = true; - } - e.stopPropagation(); - } - - 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 ( 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 ( -
- Image not found - {this.lightbox(path)} -
) - } -} \ No newline at end of file diff --git a/src/views/nodes/NodeView.scss b/src/views/nodes/NodeView.scss deleted file mode 100644 index dac1c0a8e..000000000 --- a/src/views/nodes/NodeView.scss +++ /dev/null @@ -1,23 +0,0 @@ -.node { - position: absolute; - background: #cdcdcd; - overflow: hidden; - &.minimized { - width: 30px; - height: 30px; - } - .top { - background: #232323; - height: 20px; - cursor: pointer; - } - .content { - padding: 20px 20px; - height: auto; - box-sizing: border-box; - } - .scroll-box { - overflow-y: scroll; - height: calc(100% - 20px); - } -} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 1a7c9286e..e91b0be5e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); module.exports = { devtool: 'eval', mode: 'development', - entry: "./src/Main.tsx", + entry: "./src/client/views/Main.tsx", devtool: "source-map", node: { fs: 'empty', -- cgit v1.2.3-70-g09d2 From 9f8653ab3d7f82a5d82b925bf339bef8d6723f5c Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 11 Feb 2019 17:37:03 -0500 Subject: added framework for annotation overlays -- see ImageBox --- src/client/documents/Documents.ts | 14 +++-- src/client/views/Main.tsx | 12 ++-- .../views/collections/CollectionDockingView.tsx | 4 +- .../views/collections/CollectionFreeFormView.tsx | 69 ++++++++++++++-------- .../views/collections/CollectionSchemaView.tsx | 14 +++-- .../views/collections/CollectionViewBase.tsx | 1 + .../views/nodes/CollectionFreeFormDocumentView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 20 ++++++- src/client/views/nodes/ImageBox.scss | 1 + src/client/views/nodes/ImageBox.tsx | 10 +++- src/fields/Key.ts | 3 + 11 files changed, 101 insertions(+), 50 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6925234fe..12eddaec9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -115,11 +115,13 @@ export namespace Documents { 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)); - imageProto.Set(KeyStore.Height, new NumberField(300)); - imageProto.Set(KeyStore.Layout, new TextField(ImageBox.LayoutString())); + imageProto.Set(KeyStore.NativeWidth, new NumberField(606)); + imageProto.Set(KeyStore.Width, new NumberField(606)); + imageProto.Set(KeyStore.Height, new NumberField(446)); + imageProto.Set(KeyStore.Layout, new TextField("")); + imageProto.Set(KeyStore.AnnotatedLayout, new TextField(ImageBox.LayoutString())); // imageProto.SetField(KeyStore.Layout, new TextField('
')); - imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data])); + imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations])); Server.AddDocument(imageProto); return imageProto; } @@ -130,6 +132,10 @@ export namespace Documents { let doc = GetImagePrototype().MakeDelegate(); setupOptions(doc, options); doc.Set(KeyStore.Data, new ImageField(new URL(url))); + + let annotation = Documents.TextDocument({ title: "hello" }); + Server.AddDocument(annotation); + doc.Set(KeyStore.Annotations, new ListField([annotation])); Server.AddDocument(doc); var sdoc = Server.GetField(doc.Id) as Document; return sdoc; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 9a359e868..31a709744 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -43,11 +43,11 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { let doc2 = doc1.MakeDelegate(); doc2.Set(KS.X, new NumberField(150)); doc2.Set(KS.Y, new NumberField(20)); - let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { + let doc3 = Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { x: 450, y: 100, title: "cat 1" }); - doc3.Set(KeyStore.Data, new ImageField); - 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", { + //doc3.Set(KeyStore.Data, new ImageField); + const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v })); schemaDocs[0].SetData(KS.Author, "Tyler", TextField); @@ -61,7 +61,7 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // 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(doc4);//, doc1, doc3); + let docset2 = [doc4, doc1, doc3]; let doc6 = Documents.CollectionDocument(docset2, { x: 350, y: 100, width: 600, height: 600, title: "docking collection" }); @@ -76,7 +76,7 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { // 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); } //} @@ -84,7 +84,7 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { ReactDOM.render((
- +
), diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9aee9c10f..037b85712 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -95,7 +95,7 @@ export class CollectionDockingView extends CollectionViewBase { const value: Document[] = Document.GetData(fieldKey, ListField, []); for (var i: number = 0; i < value.length; i++) { if (value[i].Id === component) { - return (); + return (); } } if (component === "text") { @@ -236,7 +236,7 @@ export class CollectionDockingView extends CollectionViewBase { container.getElement().html("
"); setTimeout(function () { ReactDOM.render(( - + ), document.getElementById(containingDiv) ); diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 9cf29d000..9cf8f2e35 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -22,11 +22,25 @@ export class CollectionFreeFormView extends CollectionViewBase { private _nodeContainerRef = React.createRef(); private _lastX: number = 0; private _lastY: number = 0; + private _downX: number = 0; + private _downY: number = 0; constructor(props: CollectionViewProps) { super(props); } + @computed + get isAnnotationOverlay() { return this.props.CollectionFieldKey == KeyStore.Annotations; } + + @computed + get nativeWidth() { return this.props.DocumentForCollection.GetNumber(KeyStore.NativeWidth, 0); } + + @computed + get zoomScaling() { return this.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); } + + @computed + get resizeScaling() { return this.isAnnotationOverlay ? this.props.DocumentForCollection.GetNumber(KeyStore.Width, 0) / this.nativeWidth : 1; } + @action drop = (e: Event, de: DragManager.DropEvent) => { const doc = de.data["document"]; @@ -38,12 +52,12 @@ export class CollectionFreeFormView extends CollectionViewBase { } 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 { translateX, translateY } = Utils.GetScreenTransform(this._canvasRef.current!); + const currScale = this.resizeScaling * this.zoomScaling * this.props.ContainingDocumentView!.ScalingToScreenSpace; const screenX = de.x - xOffset; const screenY = de.y - yOffset; - const docX = (screenX - translateX) / sscale / scale; - const docY = (screenY - translateY) / sscale / scale; + const docX = (screenX - translateX) / currScale; + const docY = (screenY - translateY) / currScale; doc.x = docX; doc.y = docY; this.bringToFront(doc); @@ -69,8 +83,8 @@ export class CollectionFreeFormView extends CollectionViewBase { document.addEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointerup", this.onPointerUp); - this._lastX = e.pageX; - this._lastY = e.pageY; + this._downX = this._lastX = e.pageX; + this._downY = this._lastY = e.pageY; } } @@ -79,20 +93,23 @@ export class CollectionFreeFormView extends CollectionViewBase { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); e.stopPropagation(); - SelectionManager.DeselectAll(); + if (Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) { + if (!SelectionManager.IsSelected(this.props.ContainingDocumentView as CollectionFreeFormDocumentView)) { + SelectionManager.SelectDoc(this.props.ContainingDocumentView as CollectionFreeFormDocumentView, false); + } + } } @action onPointerMove = (e: PointerEvent): void => { - var me = this; if (!e.cancelBubble && this.active) { e.preventDefault(); e.stopPropagation(); let currScale: number = this.props.ContainingDocumentView!.ScalingToScreenSpace; - let x = this.props.DocumentForCollection.GetData(KeyStore.PanX, NumberField, Number(0)); - let y = this.props.DocumentForCollection.GetData(KeyStore.PanY, NumberField, Number(0)); - this.props.DocumentForCollection.SetData(KeyStore.PanX, x + (e.pageX - this._lastX) / currScale, NumberField); - this.props.DocumentForCollection.SetData(KeyStore.PanY, y + (e.pageY - this._lastY) / currScale, NumberField); + let x = this.props.DocumentForCollection.GetNumber(KeyStore.PanX, 0); + let y = this.props.DocumentForCollection.GetNumber(KeyStore.PanY, 0); + + this.SetPan(x + (e.pageX - this._lastX) / currScale, y + (e.pageY - this._lastY) / currScale); } this._lastX = e.pageX; this._lastY = e.pageY; @@ -105,16 +122,18 @@ export class CollectionFreeFormView extends CollectionViewBase { 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; + var newDeltaScale = this.isAnnotationOverlay ? Math.max(1, deltaScale) : deltaScale; - var newContainerX = LocalX * deltaScale + Panxx + Xx; - var newContainerY = LocalY * deltaScale + Panyy + Yy; - - let dx = ContainerX - newContainerX; - let dy = ContainerY - newContainerY; + this.props.DocumentForCollection.SetNumber(KeyStore.Scale, newDeltaScale); + this.SetPan(ContainerX - (LocalX * newDeltaScale + Xx), ContainerY - (LocalY * newDeltaScale + Yy)); + } - this.props.DocumentForCollection.Set(KeyStore.Scale, new NumberField(deltaScale)); - this.props.DocumentForCollection.SetData(KeyStore.PanX, Panxx + dx, NumberField); - this.props.DocumentForCollection.SetData(KeyStore.PanY, Panyy + dy, NumberField); + @action + private SetPan(panX: number, panY: number) { + const newPanX = Math.max(-(this.resizeScaling * this.zoomScaling - this.resizeScaling) * this.nativeWidth, Math.min(0, panX)); + const newPanY = Math.min(0, panY); + this.props.DocumentForCollection.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); + this.props.DocumentForCollection.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); } @action @@ -173,11 +192,10 @@ export class CollectionFreeFormView extends CollectionViewBase { } render() { - const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; - const value: Document[] = Document.GetList(fieldKey, []); + const Document: Document = this.props.DocumentForCollection; + const value: Document[] = Document.GetList(this.props.CollectionFieldKey, []); const panx: number = Document.GetNumber(KeyStore.PanX, 0); const pany: number = Document.GetNumber(KeyStore.PanY, 0); - const currScale: number = Document.GetNumber(KeyStore.Scale, 1); return (
-
+
+ {this.props.BackgroundView} {value.map(doc => { - return (); + return (); })}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 2d5bd6c99..b2ee2f5f2 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -86,10 +86,11 @@ export class CollectionSchemaView extends CollectionViewBase { if (target.tagName == "SPAN" && target.className.includes("Resizer")) { e.stopPropagation(); } - if (e.button === 2 && this.active) { - e.stopPropagation(); - e.preventDefault(); - } else { + // if (e.button === 2 && this.active) { + // e.stopPropagation(); + // e.preventDefault(); + // } else + { if (e.buttons === 1 && this.active) { e.stopPropagation(); } @@ -104,7 +105,7 @@ export class CollectionSchemaView extends CollectionViewBase { let content; if (this.selectedIndex != -1) { content = ( - + ) } else { content =
@@ -119,7 +120,8 @@ export class CollectionSchemaView extends CollectionViewBase { page={0} showPagination={false} style={{ - display: "inline-block" + display: "inline-block", + width: "100%" }} columns={columns.map(col => { return ( diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 09e8ec729..e854d3077 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -16,6 +16,7 @@ export interface CollectionViewProps { CollectionFieldKey: Key; DocumentForCollection: Document; ContainingDocumentView: Opt; + BackgroundView: Opt; } export const COLLECTION_BORDER_WIDTH = 2; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1d53cedc4..a183db828 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -48,7 +48,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { @computed get transform(): string { - return `translate(${this.x}px, ${this.y}px)`; + return `scale(${this.props.Scaling}, ${this.props.Scaling}) translate(${this.x}px, ${this.y}px)`; } @computed @@ -207,6 +207,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; return (
// needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way? ContainingCollectionView: Opt; + Scaling: number; } @observer export class DocumentView extends React.Component { @@ -34,6 +35,11 @@ export class DocumentView extends React.Component { return this.props.Document.GetData(KeyStore.Layout, TextField, String("

Error loading layout data

")); } + @computed + get annotatedlayout(): string { + return this.props.Document.GetData(KeyStore.AnnotatedLayout, TextField, String("

Error loading layout data

")); + } + @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array()); @@ -51,7 +57,7 @@ export class DocumentView extends React.Component { public get ScalingToScreenSpace(): number { if (this.props.ContainingCollectionView != undefined && this.props.ContainingCollectionView.props.ContainingDocumentView != undefined) { - let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetData(KeyStore.Scale, NumberField, Number(1)); + let ss = this.props.ContainingCollectionView.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); return this.props.ContainingCollectionView.props.ContainingDocumentView.ScalingToScreenSpace * ss; } return 1; @@ -81,7 +87,7 @@ export class DocumentView extends React.Component { Yy = ry - COLLECTION_BORDER_WIDTH; } - let Ss = this.props.Document.GetData(KeyStore.Scale, NumberField, Number(1)); + let Ss = this.props.Document.GetNumber(KeyStore.Scale, 1); let Panxx = this.props.Document.GetData(KeyStore.PanX, NumberField, Number(0)); let Panyy = this.props.Document.GetData(KeyStore.PanY, NumberField, Number(0)); let LocalX = (ContainerX - (Xx + Panxx)) / Ss; @@ -115,7 +121,7 @@ export class DocumentView extends React.Component { // 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 ss = containingDocView.props.Document.GetData(KeyStore.Scale, NumberField, Number(1)); + let ss = containingDocView.props.Document.GetNumber(KeyStore.Scale, 1) * this.props.Scaling; 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; let { ScreenX, ScreenY } = containingDocView.TransformToScreenPoint(parentX, parentY, ss, panxx, panyy); @@ -138,6 +144,14 @@ export class DocumentView extends React.Component { 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. } + var annotated = { console.log(test) }} + />; + bindings["BackgroundView"] = this.annotatedlayout ? annotated : null; return (
{ @@ -67,7 +68,9 @@ export class ImageBox extends React.Component { 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 })} + onCloseRequest={action(() => + this._isOpen = false + )} onMovePrevRequest={action(() => this._photoIndex = (this._photoIndex + images.length - 1) % images.length )} @@ -82,10 +85,11 @@ export class ImageBox extends React.Component { 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"; + let width = this.props.doc.GetNumber(KeyStore.Width, 1); return (
- Image not found + Image not found {this.lightbox(path)}
) } diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 993102613..67a5f86fb 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -42,11 +42,14 @@ export namespace KeyStore { export const PanX = new Key("PanX"); export const PanY = new Key("PanY"); export const Scale = new Key("Scale"); + export const NativeWidth = new Key("NativeWidth"); export const Width = new Key("Width"); export const Height = new Key("Height"); export const ZIndex = new Key("ZIndex"); export const Data = new Key("Data"); + export const Annotations = new Key("Annotations"); export const Layout = new Key("Layout"); + export const AnnotatedLayout = new Key("AnnotatedLayout"); export const LayoutKeys = new Key("LayoutKeys"); export const LayoutFields = new Key("LayoutFields"); export const ColumnsKey = new Key("SchemaColumns"); -- cgit v1.2.3-70-g09d2 From 793cc99ab134bc5aa4667c95def851ce08d35146 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 Feb 2019 21:17:24 -0500 Subject: added aspect resizing. fixed pinch zoom for macs --- src/client/documents/Documents.ts | 15 ++++++++++++--- src/client/views/DocumentDecorations.tsx | 6 ++++-- src/client/views/Main.tsx | 4 ++-- .../views/collections/CollectionFreeFormView.tsx | 17 +++++++++++------ .../views/nodes/CollectionFreeFormDocumentView.tsx | 21 ++++++++++++++++++--- src/fields/Key.ts | 1 + 6 files changed, 48 insertions(+), 16 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 12eddaec9..84aaaa78f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -17,6 +17,8 @@ interface DocumentOptions { y?: number; width?: number; height?: number; + nativeWidth?: number; + nativeHeight?: number; title?: string; } @@ -34,6 +36,12 @@ export namespace Documents { if (options.height) { doc.SetData(KeyStore.Height, options.height, NumberField); } + if (options.nativeWidth) { + doc.SetData(KeyStore.NativeWidth, options.nativeWidth, NumberField); + } + if (options.nativeHeight) { + doc.SetData(KeyStore.NativeHeight, options.nativeHeight, NumberField); + } if (options.title) { doc.SetData(KeyStore.Title, options.title, TextField); } @@ -115,9 +123,10 @@ export namespace Documents { 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.NativeWidth, new NumberField(606)); - imageProto.Set(KeyStore.Width, new NumberField(606)); - imageProto.Set(KeyStore.Height, new NumberField(446)); + imageProto.Set(KeyStore.NativeWidth, new NumberField(300)); + imageProto.Set(KeyStore.NativeHeight, new NumberField(300)); + imageProto.Set(KeyStore.Width, new NumberField(300)); + imageProto.Set(KeyStore.Height, new NumberField(300)); imageProto.Set(KeyStore.Layout, new TextField("")); imageProto.Set(KeyStore.AnnotatedLayout, new TextField(ImageBox.LayoutString())); // imageProto.SetField(KeyStore.Layout, new TextField('
')); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8a94bff36..7efaa5533 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -111,8 +111,10 @@ export class DocumentDecorations extends React.Component { let actualdH = Math.max(element.height + (dH * scale), 20); element.x += dX * (actualdW - element.width); element.y += dY * (actualdH - element.height); - element.width = actualdW; - element.height = actualdH; + if (Math.abs(dW) > Math.abs(dH)) + element.width = actualdW; + else + element.height = actualdH; } }) } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 31a709744..8d474698f 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -44,11 +44,11 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { doc2.Set(KS.X, new NumberField(150)); doc2.Set(KS.Y, new NumberField(20)); let doc3 = Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { - x: 450, y: 100, title: "cat 1" + x: 450, y: 100, title: "cat 1", width: 606, height: 386, nativeWidth: 606, nativeHeight: 386 }); //doc3.Set(KeyStore.Data, new ImageField); const schemaDocs = Array.from(Array(5).keys()).map(v => Documents.ImageDocument("https://psmag.com/.image/t_share/MTMyNzc2NzM1MDY1MjgzMDM4/shutterstock_151341212jpg.jpg", { - x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v + x: 50 + 100 * v, y: 50, width: 100, height: 100, title: "cat" + v, nativeWidth: 606, nativeHeight: 386 })); schemaDocs[0].SetData(KS.Author, "Tyler", TextField); schemaDocs[4].SetData(KS.Author, "Bob", TextField); diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 9cf8f2e35..bf24965dc 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -34,6 +34,8 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get nativeWidth() { return this.props.DocumentForCollection.GetNumber(KeyStore.NativeWidth, 0); } + @computed + get nativeHeight() { return this.props.DocumentForCollection.GetNumber(KeyStore.NativeHeight, 0); } @computed get zoomScaling() { return this.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); } @@ -56,10 +58,8 @@ export class CollectionFreeFormView extends CollectionViewBase { const currScale = this.resizeScaling * this.zoomScaling * this.props.ContainingDocumentView!.ScalingToScreenSpace; const screenX = de.x - xOffset; const screenY = de.y - yOffset; - const docX = (screenX - translateX) / currScale; - const docY = (screenY - translateY) / currScale; - doc.x = docX; - doc.y = docY; + doc.x = (screenX - translateX) / currScale; + doc.y = (screenY - translateY) / currScale; this.bringToFront(doc); } e.stopPropagation(); @@ -118,10 +118,15 @@ export class CollectionFreeFormView extends CollectionViewBase { @action onPointerWheel = (e: React.WheelEvent): void => { e.stopPropagation(); + e.preventDefault(); + let modes = ['pixels', 'lines', 'page']; + let coefficient = 1000; + if (modes[e.deltaMode] == 'pixels') coefficient = 50; + else if (modes[e.deltaMode] == 'lines') coefficient = 1000; // This should correspond to line-height?? 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; + var deltaScale = (1 - (e.deltaY / coefficient)) * Ss; var newDeltaScale = this.isAnnotationOverlay ? Math.max(1, deltaScale) : deltaScale; this.props.DocumentForCollection.SetNumber(KeyStore.Scale, newDeltaScale); @@ -131,7 +136,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action private SetPan(panX: number, panY: number) { const newPanX = Math.max(-(this.resizeScaling * this.zoomScaling - this.resizeScaling) * this.nativeWidth, Math.min(0, panX)); - const newPanY = Math.min(0, panY); + const newPanY = Math.max(-(this.resizeScaling * this.zoomScaling - this.resizeScaling) * this.nativeHeight, Math.min(0, panY)); this.props.DocumentForCollection.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX); this.props.DocumentForCollection.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index a183db828..fafb470f9 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -53,20 +53,35 @@ export class CollectionFreeFormDocumentView extends DocumentView { @computed get width(): number { - return this.props.Document.GetData(KeyStore.Width, NumberField, Number(0)); + return this.props.Document.GetNumber(KeyStore.Width, 0); + } + + @computed + get nativeWidth(): number { + return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); } set width(w: number) { this.props.Document.SetData(KeyStore.Width, w, NumberField) + if (this.nativeWidth > 0 && this.nativeHeight > 0) { + this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w) + } } @computed get height(): number { - return this.props.Document.GetData(KeyStore.Height, NumberField, Number(0)); + return this.props.Document.GetNumber(KeyStore.Height, 0); + } + @computed + get nativeHeight(): number { + return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); } set height(h: number) { - this.props.Document.SetData(KeyStore.Height, h, NumberField) + this.props.Document.SetData(KeyStore.Height, h, NumberField); + if (this.nativeWidth > 0 && this.nativeHeight > 0) { + this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h) + } } @computed diff --git a/src/fields/Key.ts b/src/fields/Key.ts index 67a5f86fb..13bdd01d4 100644 --- a/src/fields/Key.ts +++ b/src/fields/Key.ts @@ -43,6 +43,7 @@ export namespace KeyStore { export const PanY = new Key("PanY"); export const Scale = new Key("Scale"); export const NativeWidth = new Key("NativeWidth"); + export const NativeHeight = new Key("NativeHeight"); export const Width = new Key("Width"); export const Height = new Key("Height"); export const ZIndex = new Key("ZIndex"); -- cgit v1.2.3-70-g09d2 From f3359bd32f8c4ecb612d3c95f198d5e248ab6450 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 11 Feb 2019 21:28:09 -0500 Subject: renamed AnnotatedLaout to BackgroundLayout --- src/client/documents/Documents.ts | 2 +- src/client/views/nodes/DocumentView.tsx | 8 ++++---- src/fields/Key.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src/fields') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 84aaaa78f..8fbf99da6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -128,7 +128,7 @@ export namespace Documents { imageProto.Set(KeyStore.Width, new NumberField(300)); imageProto.Set(KeyStore.Height, new NumberField(300)); imageProto.Set(KeyStore.Layout, new TextField("")); - imageProto.Set(KeyStore.AnnotatedLayout, new TextField(ImageBox.LayoutString())); + imageProto.Set(KeyStore.BackgroundLayout, new TextField(ImageBox.LayoutString())); // imageProto.SetField(KeyStore.Layout, new TextField('
')); imageProto.Set(KeyStore.LayoutKeys, new ListField([KeyStore.Data, KeyStore.Annotations])); Server.AddDocument(imageProto); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bba5becc3..a2f8ceebe 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -36,8 +36,8 @@ export class DocumentView extends React.Component { } @computed - get annotatedlayout(): string { - return this.props.Document.GetData(KeyStore.AnnotatedLayout, TextField, String("

Error loading layout data

")); + get backgroundLayout(): string { + return this.props.Document.GetData(KeyStore.BackgroundLayout, TextField, String("

Error loading layout data

")); } @computed @@ -147,11 +147,11 @@ export class DocumentView extends React.Component { var annotated = { console.log(test) }} />; - bindings["BackgroundView"] = this.annotatedlayout ? annotated : null; + bindings["BackgroundView"] = this.backgroundLayout ? annotated : null; return (
Date: Mon, 11 Feb 2019 21:42:49 -0500 Subject: Changed FieldId type around to clean some stuff up --- src/client/Server.ts | 23 ++++++++++++---------- src/client/SocketStub.ts | 6 +++--- src/client/documents/Documents.ts | 4 ++-- src/client/views/nodes/AnnotationView.tsx | 12 ----------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 3 ++- src/client/views/nodes/FormattedTextBox.tsx | 1 + src/fields/Document.ts | 7 +++---- src/fields/Field.ts | 10 +++++----- 8 files changed, 29 insertions(+), 37 deletions(-) delete mode 100644 src/client/views/nodes/AnnotationView.tsx (limited to 'src/fields') diff --git a/src/client/Server.ts b/src/client/Server.ts index 85e55a84e..0cb6e17c2 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -1,16 +1,16 @@ -import { Field, FieldWaiting, FIELD_ID, FIELD_WAITING, FieldValue } from "../fields/Field" +import { Field, FieldWaiting, FieldId, FIELD_WAITING, FieldValue, Opt } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" import { SocketStub } from "./SocketStub"; export class Server { - private static ClientFieldsCached: ObservableMap = new ObservableMap(); + private static ClientFieldsCached: ObservableMap = new ObservableMap(); // Retrieves the cached value of the field and sends a request to the server for the real value (if it's not cached). // Call this is from within a reaction and test whether the return value is FieldWaiting. // 'hackTimeout' is here temporarily for simplicity when debugging things. - public static GetField(fieldid: FIELD_ID, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { + public static GetField(fieldid: FieldId, callback: (field: Field) => void = (f) => { }, hackTimeout: number = -1) { if (!this.ClientFieldsCached.get(fieldid)) { this.ClientFieldsCached.set(fieldid, FieldWaiting); //simulating a server call with a registered callback action @@ -24,15 +24,18 @@ export class Server { } static times = 0; // hack for testing - public static GetDocumentField(doc: Document, key: Key) { + public static GetDocumentField(doc: Document, key: Key): FieldValue { var hackTimeout: number = key == KeyStore.Data ? (this.times++ == 0 ? 5000 : 1000) : key == KeyStore.X ? 2500 : 500; - return this.GetField(doc._proxies.get(key), - action((fieldfromserver: Field) => { - doc._proxies.delete(key); - doc.fields.set(key, fieldfromserver); - }) - , hackTimeout); + let fieldId = doc._proxies.get(key); + if (fieldId) { + return this.GetField(fieldId, + action((fieldfromserver: Field) => { + doc._proxies.delete(key); + doc.fields.set(key, fieldfromserver); + }) + , hackTimeout); + } } public static AddDocument(document: Document) { diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 58dedbf82..cea30cb8b 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -1,11 +1,11 @@ -import { Field, FIELD_ID } from "../fields/Field" +import { Field, FieldId } from "../fields/Field" import { Key, KeyStore } from "../fields/Key" import { ObservableMap, action } from "mobx"; import { Document } from "../fields/Document" export class SocketStub { - static FieldStore: ObservableMap = new ObservableMap(); + static FieldStore: ObservableMap = new ObservableMap(); public static SEND_ADD_DOCUMENT(document: Document) { // Send a serialized version of the document to the server @@ -19,7 +19,7 @@ export class SocketStub { document.fields.forEach((f, key) => (this.FieldStore.get(document.Id) as Document)._proxies.set(key, (f as Field).Id)); } - public static SEND_FIELD_REQUEST(fieldid: FIELD_ID, callback: (field: Field) => void, timeout: number) { + public static SEND_FIELD_REQUEST(fieldid: FieldId, callback: (field: Field) => void, timeout: number) { if (timeout < 0)// this is a hack to make things easier to setup until we have a server... won't be neededa fter that. callback(this.FieldStore.get(fieldid) as Field); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6925234fe..575112c85 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -10,7 +10,7 @@ import { CollectionSchemaView } from "../views/collections/CollectionSchemaView" import { ImageField } from "../../fields/ImageField"; import { ImageBox } from "../views/nodes/ImageBox"; import { CollectionFreeFormView } from "../views/collections/CollectionFreeFormView"; -import { FIELD_ID } from "../../fields/Field"; +import { FieldId } from "../../fields/Field"; interface DocumentOptions { x?: number; @@ -107,7 +107,7 @@ export namespace Documents { } - let imageProtoId: FIELD_ID; + let imageProtoId: FieldId; function GetImagePrototype(): Document { if (imageProtoId === undefined) { let imageProto = new Document(); diff --git a/src/client/views/nodes/AnnotationView.tsx b/src/client/views/nodes/AnnotationView.tsx deleted file mode 100644 index 2e50370a1..000000000 --- a/src/client/views/nodes/AnnotationView.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React = require('react') -import { CollectionViewProps } from '../collections/CollectionViewBase'; - -export class AnnotationView extends React.Component { - - render() { - return ( - <> - ); - } - -} \ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 1d53cedc4..a111a9936 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -17,6 +17,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { private _contextMenuCanOpen = false; private _downX: number = 0; private _downY: number = 0; + // private _mainCont = React.createRef(); constructor(props: DocumentViewProps) { super(props); @@ -216,7 +217,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}> - +
); } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index eead43b9f..8f959762a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,6 +12,7 @@ import React = require("react") import { RichTextField } from "../../../fields/RichTextField"; import { FieldViewProps, FieldView } from "./FieldView"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; +import { observer } from "mobx-react"; // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 6f9752a8e..c682d8e94 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -1,15 +1,14 @@ -import { Field, Cast, Opt, FieldWaiting, FIELD_ID, FieldValue } from "./Field" +import { Field, Cast, Opt, FieldWaiting, FieldId, FieldValue } from "./Field" import { Key, KeyStore } from "./Key" import { NumberField } from "./NumberField"; import { ObservableMap, computed, action, observable } from "mobx"; import { TextField } from "./TextField"; import { ListField } from "./ListField"; -import { findDOMNode } from "react-dom"; import { Server } from "../client/Server"; export class Document extends Field { - public fields: ObservableMap> = new ObservableMap(); - public _proxies: ObservableMap = new ObservableMap(); + public fields: ObservableMap = new ObservableMap(); + public _proxies: ObservableMap = new ObservableMap(); @computed public get Title() { diff --git a/src/fields/Field.ts b/src/fields/Field.ts index 6adee9b61..4a3968699 100644 --- a/src/fields/Field.ts +++ b/src/fields/Field.ts @@ -10,21 +10,21 @@ export function Cast(field: FieldValue, ctor: { new(): T return undefined; } -export let FieldWaiting: FIELD_WAITING = ""; +export const FieldWaiting: FIELD_WAITING = ""; export type FIELD_WAITING = ""; -export type FIELD_ID = string | undefined; +export type FieldId = string; export type Opt = T | undefined; export type FieldValue = Opt | FIELD_WAITING; export abstract class Field { //FieldUpdated: TypedEvent> = new TypedEvent>(); - private id: FIELD_ID; - get Id(): FIELD_ID { + private id: FieldId; + get Id(): FieldId { return this.id; } - constructor(id: FIELD_ID = undefined) { + constructor(id: Opt = undefined) { this.id = id || Utils.GenerateGuid(); } -- cgit v1.2.3-70-g09d2