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/util/Scripting.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'src/util/Scripting.ts') 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 -- 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/util/Scripting.ts') 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/util/Scripting.ts') 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